xref: /onnv-gate/usr/src/lib/libtecla/common/pcache.c (revision 0:68f95e015346)
1*0Sstevel@tonic-gate /*
2*0Sstevel@tonic-gate  * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd.
3*0Sstevel@tonic-gate  *
4*0Sstevel@tonic-gate  * All rights reserved.
5*0Sstevel@tonic-gate  *
6*0Sstevel@tonic-gate  * Permission is hereby granted, free of charge, to any person obtaining a
7*0Sstevel@tonic-gate  * copy of this software and associated documentation files (the
8*0Sstevel@tonic-gate  * "Software"), to deal in the Software without restriction, including
9*0Sstevel@tonic-gate  * without limitation the rights to use, copy, modify, merge, publish,
10*0Sstevel@tonic-gate  * distribute, and/or sell copies of the Software, and to permit persons
11*0Sstevel@tonic-gate  * to whom the Software is furnished to do so, provided that the above
12*0Sstevel@tonic-gate  * copyright notice(s) and this permission notice appear in all copies of
13*0Sstevel@tonic-gate  * the Software and that both the above copyright notice(s) and this
14*0Sstevel@tonic-gate  * permission notice appear in supporting documentation.
15*0Sstevel@tonic-gate  *
16*0Sstevel@tonic-gate  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17*0Sstevel@tonic-gate  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18*0Sstevel@tonic-gate  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
19*0Sstevel@tonic-gate  * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
20*0Sstevel@tonic-gate  * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL
21*0Sstevel@tonic-gate  * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING
22*0Sstevel@tonic-gate  * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
23*0Sstevel@tonic-gate  * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
24*0Sstevel@tonic-gate  * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
25*0Sstevel@tonic-gate  *
26*0Sstevel@tonic-gate  * Except as contained in this notice, the name of a copyright holder
27*0Sstevel@tonic-gate  * shall not be used in advertising or otherwise to promote the sale, use
28*0Sstevel@tonic-gate  * or other dealings in this Software without prior written authorization
29*0Sstevel@tonic-gate  * of the copyright holder.
30*0Sstevel@tonic-gate  */
31*0Sstevel@tonic-gate 
32*0Sstevel@tonic-gate /*
33*0Sstevel@tonic-gate  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
34*0Sstevel@tonic-gate  * Use is subject to license terms.
35*0Sstevel@tonic-gate  */
36*0Sstevel@tonic-gate 
37*0Sstevel@tonic-gate #pragma ident	"%Z%%M%	%I%	%E% SMI"
38*0Sstevel@tonic-gate 
39*0Sstevel@tonic-gate /*
40*0Sstevel@tonic-gate  * If file-system access is to be excluded, this module has no function,
41*0Sstevel@tonic-gate  * so all of its code should be excluded.
42*0Sstevel@tonic-gate  */
43*0Sstevel@tonic-gate #ifndef WITHOUT_FILE_SYSTEM
44*0Sstevel@tonic-gate 
45*0Sstevel@tonic-gate #include <stdlib.h>
46*0Sstevel@tonic-gate #include <string.h>
47*0Sstevel@tonic-gate #include <stdio.h>
48*0Sstevel@tonic-gate #include <errno.h>
49*0Sstevel@tonic-gate 
50*0Sstevel@tonic-gate #include "libtecla.h"
51*0Sstevel@tonic-gate #include "pathutil.h"
52*0Sstevel@tonic-gate #include "homedir.h"
53*0Sstevel@tonic-gate #include "freelist.h"
54*0Sstevel@tonic-gate #include "direader.h"
55*0Sstevel@tonic-gate #include "stringrp.h"
56*0Sstevel@tonic-gate #include "errmsg.h"
57*0Sstevel@tonic-gate 
58*0Sstevel@tonic-gate /*
59*0Sstevel@tonic-gate  * The new_PcaPathConf() constructor sets the integer first member of
60*0Sstevel@tonic-gate  * the returned object to the following magic number. This is then
61*0Sstevel@tonic-gate  * checked for by pca_path_completions() as a sanity check.
62*0Sstevel@tonic-gate  */
63*0Sstevel@tonic-gate #define PPC_ID_CODE 4567
64*0Sstevel@tonic-gate 
65*0Sstevel@tonic-gate /*
66*0Sstevel@tonic-gate  * A pointer to a structure of the following type can be passed to
67*0Sstevel@tonic-gate  * the builtin path-completion callback function to modify its behavior.
68*0Sstevel@tonic-gate  */
69*0Sstevel@tonic-gate struct PcaPathConf {
70*0Sstevel@tonic-gate   int id;          /* This is set to PPC_ID_CODE by new_PcaPathConf() */
71*0Sstevel@tonic-gate   PathCache *pc;   /* The path-list cache in which to look up the executables */
72*0Sstevel@tonic-gate   int escaped;     /* If non-zero, backslashes in the input line are */
73*0Sstevel@tonic-gate                    /*  interpreted as escaping special characters and */
74*0Sstevel@tonic-gate                    /*  spaces, and any special characters and spaces in */
75*0Sstevel@tonic-gate                    /*  the listed completions will also be escaped with */
76*0Sstevel@tonic-gate                    /*  added backslashes. This is the default behaviour. */
77*0Sstevel@tonic-gate                    /* If zero, backslashes are interpreted as being */
78*0Sstevel@tonic-gate                    /*  literal parts of the file name, and none are added */
79*0Sstevel@tonic-gate                    /*  to the completion suffixes. */
80*0Sstevel@tonic-gate   int file_start;  /* The index in the input line of the first character */
81*0Sstevel@tonic-gate                    /*  of the file name. If you specify -1 here, */
82*0Sstevel@tonic-gate                    /*  pca_path_completions() identifies the */
83*0Sstevel@tonic-gate                    /*  the start of the file by looking backwards for */
84*0Sstevel@tonic-gate                    /*  an unescaped space, or the beginning of the line. */
85*0Sstevel@tonic-gate };
86*0Sstevel@tonic-gate 
87*0Sstevel@tonic-gate /*
88*0Sstevel@tonic-gate  * Prepended to each chached filename is a character which contains
89*0Sstevel@tonic-gate  * one of the following status codes. When a given filename (minus
90*0Sstevel@tonic-gate  * this byte) is passed to the application's check_fn(), the result
91*0Sstevel@tonic-gate  * is recorded in this byte, such that the next time it is looked
92*0Sstevel@tonic-gate  * up, we don't have to call check_fn() again. These codes are cleared
93*0Sstevel@tonic-gate  * whenever the path is scanned and whenever the check_fn() callback
94*0Sstevel@tonic-gate  * is changed.
95*0Sstevel@tonic-gate  */
96*0Sstevel@tonic-gate typedef enum {
97*0Sstevel@tonic-gate   PCA_F_ENIGMA='?', /* The file remains to be checked */
98*0Sstevel@tonic-gate   PCA_F_WANTED='+', /* The file has been selected by the caller's callback */
99*0Sstevel@tonic-gate   PCA_F_IGNORE='-'  /* The file has been rejected by the caller's callback */
100*0Sstevel@tonic-gate } PcaFileStatus;
101*0Sstevel@tonic-gate 
102*0Sstevel@tonic-gate /*
103*0Sstevel@tonic-gate  * Encapsulate the memory management objects which supply memoy for
104*0Sstevel@tonic-gate  * the arrays of filenames.
105*0Sstevel@tonic-gate  */
106*0Sstevel@tonic-gate typedef struct {
107*0Sstevel@tonic-gate   StringGroup *sg;       /* The memory used to record the names of files */
108*0Sstevel@tonic-gate   size_t files_dim;      /* The allocated size of files[] */
109*0Sstevel@tonic-gate   char **files;          /* Memory for 'files_dim' pointers to files */
110*0Sstevel@tonic-gate   size_t nfiles;         /* The number of filenames currently in files[] */
111*0Sstevel@tonic-gate } CacheMem;
112*0Sstevel@tonic-gate 
113*0Sstevel@tonic-gate static CacheMem *new_CacheMem(void);
114*0Sstevel@tonic-gate static CacheMem *del_CacheMem(CacheMem *cm);
115*0Sstevel@tonic-gate static void rst_CacheMem(CacheMem *cm);
116*0Sstevel@tonic-gate 
117*0Sstevel@tonic-gate /*
118*0Sstevel@tonic-gate  * Lists of nodes of the following type are used to record the
119*0Sstevel@tonic-gate  * names and contents of individual directories.
120*0Sstevel@tonic-gate  */
121*0Sstevel@tonic-gate typedef struct PathNode PathNode;
122*0Sstevel@tonic-gate struct PathNode {
123*0Sstevel@tonic-gate   PathNode *next;   /* The next directory in the path */
124*0Sstevel@tonic-gate   int relative;     /* True if the directory is a relative pathname */
125*0Sstevel@tonic-gate   CacheMem *mem;    /* The memory used to store dir[] and files[] */
126*0Sstevel@tonic-gate   char *dir;        /* The directory pathname (stored in pc->sg) */
127*0Sstevel@tonic-gate   int nfile;        /* The number of filenames stored in 'files' */
128*0Sstevel@tonic-gate   char **files;     /* Files of interest in the current directory, */
129*0Sstevel@tonic-gate                     /*  or NULL if dir[] is a relative pathname */
130*0Sstevel@tonic-gate                     /*  who's contents can't be cached. This array */
131*0Sstevel@tonic-gate                     /*  and its contents are taken from pc->abs_mem */
132*0Sstevel@tonic-gate                     /*  or pc->rel_mem */
133*0Sstevel@tonic-gate };
134*0Sstevel@tonic-gate 
135*0Sstevel@tonic-gate /*
136*0Sstevel@tonic-gate  * Append a new node to the list of directories in the path.
137*0Sstevel@tonic-gate  */
138*0Sstevel@tonic-gate static int add_PathNode(PathCache *pc, const char *dirname);
139*0Sstevel@tonic-gate 
140*0Sstevel@tonic-gate /*
141*0Sstevel@tonic-gate  * Set the maximum length allowed for usernames.
142*0Sstevel@tonic-gate  * names.
143*0Sstevel@tonic-gate  */
144*0Sstevel@tonic-gate #define USR_LEN 100
145*0Sstevel@tonic-gate 
146*0Sstevel@tonic-gate /*
147*0Sstevel@tonic-gate  * PathCache objects encapsulate the resources needed to record
148*0Sstevel@tonic-gate  * files of interest from comma-separated lists of directories.
149*0Sstevel@tonic-gate  */
150*0Sstevel@tonic-gate struct PathCache {
151*0Sstevel@tonic-gate   ErrMsg *err;           /* The error reporting buffer */
152*0Sstevel@tonic-gate   FreeList *node_mem;    /* A free-list of PathNode objects */
153*0Sstevel@tonic-gate   CacheMem *abs_mem;     /* Memory for the filenames of absolute paths */
154*0Sstevel@tonic-gate   CacheMem *rel_mem;     /* Memory for the filenames of relative paths */
155*0Sstevel@tonic-gate   PathNode *head;        /* The head of the list of directories in the */
156*0Sstevel@tonic-gate                          /*  path, or NULL if no path has been scanned yet. */
157*0Sstevel@tonic-gate   PathNode *tail;        /* The tail of the list of directories in the */
158*0Sstevel@tonic-gate                          /*  path, or NULL if no path has been scanned yet. */
159*0Sstevel@tonic-gate   PathName *path;        /* The fully qualified name of a file */
160*0Sstevel@tonic-gate   HomeDir *home;         /* Home-directory lookup object */
161*0Sstevel@tonic-gate   DirReader *dr;         /* A portable directory reader */
162*0Sstevel@tonic-gate   CplFileConf *cfc;      /* Configuration parameters to pass to */
163*0Sstevel@tonic-gate                          /*  cpl_file_completions() */
164*0Sstevel@tonic-gate   CplCheckFn *check_fn;  /* The callback used to determine if a given */
165*0Sstevel@tonic-gate                          /*  filename should be recorded in the cache. */
166*0Sstevel@tonic-gate   void *data;            /* Annonymous data to be passed to pc->check_fn() */
167*0Sstevel@tonic-gate   char usrnam[USR_LEN+1];/* The buffer used when reading the names of */
168*0Sstevel@tonic-gate                          /*  users. */
169*0Sstevel@tonic-gate };
170*0Sstevel@tonic-gate 
171*0Sstevel@tonic-gate /*
172*0Sstevel@tonic-gate  * Empty the cache.
173*0Sstevel@tonic-gate  */
174*0Sstevel@tonic-gate static void pca_clear_cache(PathCache *pc);
175*0Sstevel@tonic-gate 
176*0Sstevel@tonic-gate /*
177*0Sstevel@tonic-gate  * Read a username from string[] and record it in pc->usrnam[].
178*0Sstevel@tonic-gate  */
179*0Sstevel@tonic-gate static int pca_read_username(PathCache *pc, const char *string, int slen,
180*0Sstevel@tonic-gate 			     int literal, const char **nextp);
181*0Sstevel@tonic-gate 
182*0Sstevel@tonic-gate /*
183*0Sstevel@tonic-gate  * Extract the next component of a colon separated list of directory
184*0Sstevel@tonic-gate  * paths.
185*0Sstevel@tonic-gate  */
186*0Sstevel@tonic-gate static int pca_extract_dir(PathCache *pc, const char *path,
187*0Sstevel@tonic-gate 			   const char **nextp);
188*0Sstevel@tonic-gate 
189*0Sstevel@tonic-gate /*
190*0Sstevel@tonic-gate  * Scan absolute directories for files of interest, recording their names
191*0Sstevel@tonic-gate  * in mem->sg and recording pointers to these names in mem->files[].
192*0Sstevel@tonic-gate  */
193*0Sstevel@tonic-gate static int pca_scan_dir(PathCache *pc, const char *dirname, CacheMem *mem);
194*0Sstevel@tonic-gate 
195*0Sstevel@tonic-gate /*
196*0Sstevel@tonic-gate  * A qsort() comparison function for comparing the cached filename
197*0Sstevel@tonic-gate  * strings pointed to by two (char **) array elements. Note that
198*0Sstevel@tonic-gate  * this ignores the initial cache-status byte of each filename.
199*0Sstevel@tonic-gate  */
200*0Sstevel@tonic-gate static int pca_cmp_matches(const void *v1, const void *v2);
201*0Sstevel@tonic-gate 
202*0Sstevel@tonic-gate /*
203*0Sstevel@tonic-gate  * A qsort() comparison function for comparing a filename
204*0Sstevel@tonic-gate  * against an element of an array of pointers to filename cache
205*0Sstevel@tonic-gate  * entries.
206*0Sstevel@tonic-gate  */
207*0Sstevel@tonic-gate static int pca_cmp_file(const void *v1, const void *v2);
208*0Sstevel@tonic-gate 
209*0Sstevel@tonic-gate /*
210*0Sstevel@tonic-gate  * Initialize a PcaPathConf configuration objects with the default
211*0Sstevel@tonic-gate  * options.
212*0Sstevel@tonic-gate  */
213*0Sstevel@tonic-gate static int pca_init_PcaPathConf(PcaPathConf *ppc, PathCache *pc);
214*0Sstevel@tonic-gate 
215*0Sstevel@tonic-gate /*
216*0Sstevel@tonic-gate  * Make a copy of a completion suffix, suitable for passing to
217*0Sstevel@tonic-gate  * cpl_add_completion().
218*0Sstevel@tonic-gate  */
219*0Sstevel@tonic-gate static int pca_prepare_suffix(PathCache *pc, const char *suffix,
220*0Sstevel@tonic-gate 			      int add_escapes);
221*0Sstevel@tonic-gate 
222*0Sstevel@tonic-gate /*
223*0Sstevel@tonic-gate  * Return non-zero if the specified string appears to start with a pathname.
224*0Sstevel@tonic-gate  */
225*0Sstevel@tonic-gate static int cpa_cmd_contains_path(const char *prefix, int prefix_len);
226*0Sstevel@tonic-gate 
227*0Sstevel@tonic-gate /*
228*0Sstevel@tonic-gate  * Return a given prefix with escapes optionally removed.
229*0Sstevel@tonic-gate  */
230*0Sstevel@tonic-gate static const char *pca_prepare_prefix(PathCache *pc, const char *prefix,
231*0Sstevel@tonic-gate 				      size_t prefix_len, int escaped);
232*0Sstevel@tonic-gate 
233*0Sstevel@tonic-gate /*
234*0Sstevel@tonic-gate  * If there is a tilde expression at the beginning of the specified path,
235*0Sstevel@tonic-gate  * place the corresponding home directory into pc->path. Otherwise
236*0Sstevel@tonic-gate  * just clear pc->path.
237*0Sstevel@tonic-gate  */
238*0Sstevel@tonic-gate static int pca_expand_tilde(PathCache *pc, const char *path, int pathlen,
239*0Sstevel@tonic-gate 			    int literal, const char **endp);
240*0Sstevel@tonic-gate 
241*0Sstevel@tonic-gate /*
242*0Sstevel@tonic-gate  * Clear the filename status codes that are recorded before each filename
243*0Sstevel@tonic-gate  * in the cache.
244*0Sstevel@tonic-gate  */
245*0Sstevel@tonic-gate static void pca_remove_marks(PathCache *pc);
246*0Sstevel@tonic-gate 
247*0Sstevel@tonic-gate /*
248*0Sstevel@tonic-gate  * Specify how many PathNode's to allocate at a time.
249*0Sstevel@tonic-gate  */
250*0Sstevel@tonic-gate #define PATH_NODE_BLK 30
251*0Sstevel@tonic-gate 
252*0Sstevel@tonic-gate /*
253*0Sstevel@tonic-gate  * Specify the amount by which the files[] arrays are to be extended
254*0Sstevel@tonic-gate  * whenever they are found to be too small.
255*0Sstevel@tonic-gate  */
256*0Sstevel@tonic-gate #define FILES_BLK_FACT 256
257*0Sstevel@tonic-gate 
258*0Sstevel@tonic-gate /*.......................................................................
259*0Sstevel@tonic-gate  * Create a new object who's function is to maintain a cache of
260*0Sstevel@tonic-gate  * filenames found within a list of directories, and provide quick
261*0Sstevel@tonic-gate  * lookup and completion of selected files in this cache.
262*0Sstevel@tonic-gate  *
263*0Sstevel@tonic-gate  * Output:
264*0Sstevel@tonic-gate  *  return     PathCache *  The new, initially empty cache, or NULL
265*0Sstevel@tonic-gate  *                          on error.
266*0Sstevel@tonic-gate  */
new_PathCache(void)267*0Sstevel@tonic-gate PathCache *new_PathCache(void)
268*0Sstevel@tonic-gate {
269*0Sstevel@tonic-gate   PathCache *pc;  /* The object to be returned */
270*0Sstevel@tonic-gate /*
271*0Sstevel@tonic-gate  * Allocate the container.
272*0Sstevel@tonic-gate  */
273*0Sstevel@tonic-gate   pc = (PathCache *)malloc(sizeof(PathCache));
274*0Sstevel@tonic-gate   if(!pc) {
275*0Sstevel@tonic-gate     errno = ENOMEM;
276*0Sstevel@tonic-gate     return NULL;
277*0Sstevel@tonic-gate   };
278*0Sstevel@tonic-gate /*
279*0Sstevel@tonic-gate  * Before attempting any operation that might fail, initialize the
280*0Sstevel@tonic-gate  * container at least up to the point at which it can safely be passed
281*0Sstevel@tonic-gate  * to del_PathCache().
282*0Sstevel@tonic-gate  */
283*0Sstevel@tonic-gate   pc->err = NULL;
284*0Sstevel@tonic-gate   pc->node_mem = NULL;
285*0Sstevel@tonic-gate   pc->abs_mem = NULL;
286*0Sstevel@tonic-gate   pc->rel_mem = NULL;
287*0Sstevel@tonic-gate   pc->head = NULL;
288*0Sstevel@tonic-gate   pc->tail = NULL;
289*0Sstevel@tonic-gate   pc->path = NULL;
290*0Sstevel@tonic-gate   pc->home = NULL;
291*0Sstevel@tonic-gate   pc->dr = NULL;
292*0Sstevel@tonic-gate   pc->cfc = NULL;
293*0Sstevel@tonic-gate   pc->check_fn = 0;
294*0Sstevel@tonic-gate   pc->data = NULL;
295*0Sstevel@tonic-gate   pc->usrnam[0] = '\0';
296*0Sstevel@tonic-gate /*
297*0Sstevel@tonic-gate  * Allocate a place to record error messages.
298*0Sstevel@tonic-gate  */
299*0Sstevel@tonic-gate   pc->err = _new_ErrMsg();
300*0Sstevel@tonic-gate   if(!pc->err)
301*0Sstevel@tonic-gate     return del_PathCache(pc);
302*0Sstevel@tonic-gate /*
303*0Sstevel@tonic-gate  * Allocate the freelist of directory list nodes.
304*0Sstevel@tonic-gate  */
305*0Sstevel@tonic-gate   pc->node_mem = _new_FreeList(sizeof(PathNode), PATH_NODE_BLK);
306*0Sstevel@tonic-gate   if(!pc->node_mem)
307*0Sstevel@tonic-gate     return del_PathCache(pc);
308*0Sstevel@tonic-gate /*
309*0Sstevel@tonic-gate  * Allocate memory for recording names of files in absolute paths.
310*0Sstevel@tonic-gate  */
311*0Sstevel@tonic-gate   pc->abs_mem = new_CacheMem();
312*0Sstevel@tonic-gate   if(!pc->abs_mem)
313*0Sstevel@tonic-gate     return del_PathCache(pc);
314*0Sstevel@tonic-gate /*
315*0Sstevel@tonic-gate  * Allocate memory for recording names of files in relative paths.
316*0Sstevel@tonic-gate  */
317*0Sstevel@tonic-gate   pc->rel_mem = new_CacheMem();
318*0Sstevel@tonic-gate   if(!pc->rel_mem)
319*0Sstevel@tonic-gate     return del_PathCache(pc);
320*0Sstevel@tonic-gate /*
321*0Sstevel@tonic-gate  * Allocate a pathname buffer.
322*0Sstevel@tonic-gate  */
323*0Sstevel@tonic-gate   pc->path = _new_PathName();
324*0Sstevel@tonic-gate   if(!pc->path)
325*0Sstevel@tonic-gate     return del_PathCache(pc);
326*0Sstevel@tonic-gate /*
327*0Sstevel@tonic-gate  * Allocate an object for looking up home-directories.
328*0Sstevel@tonic-gate  */
329*0Sstevel@tonic-gate   pc->home = _new_HomeDir();
330*0Sstevel@tonic-gate   if(!pc->home)
331*0Sstevel@tonic-gate     return del_PathCache(pc);
332*0Sstevel@tonic-gate /*
333*0Sstevel@tonic-gate  * Allocate an object for reading directories.
334*0Sstevel@tonic-gate  */
335*0Sstevel@tonic-gate   pc->dr = _new_DirReader();
336*0Sstevel@tonic-gate   if(!pc->dr)
337*0Sstevel@tonic-gate     return del_PathCache(pc);
338*0Sstevel@tonic-gate /*
339*0Sstevel@tonic-gate  * Allocate a cpl_file_completions() configuration object.
340*0Sstevel@tonic-gate  */
341*0Sstevel@tonic-gate   pc->cfc = new_CplFileConf();
342*0Sstevel@tonic-gate   if(!pc->cfc)
343*0Sstevel@tonic-gate     return del_PathCache(pc);
344*0Sstevel@tonic-gate /*
345*0Sstevel@tonic-gate  * Configure cpl_file_completions() to use check_fn() to select
346*0Sstevel@tonic-gate  * files of interest.
347*0Sstevel@tonic-gate  */
348*0Sstevel@tonic-gate   cfc_set_check_fn(pc->cfc, pc->check_fn, pc->data);
349*0Sstevel@tonic-gate /*
350*0Sstevel@tonic-gate  * Return the cache, ready for use.
351*0Sstevel@tonic-gate  */
352*0Sstevel@tonic-gate   return pc;
353*0Sstevel@tonic-gate }
354*0Sstevel@tonic-gate 
355*0Sstevel@tonic-gate /*.......................................................................
356*0Sstevel@tonic-gate  * Delete a given cache of files, returning the resources that it
357*0Sstevel@tonic-gate  * was using to the system.
358*0Sstevel@tonic-gate  *
359*0Sstevel@tonic-gate  * Input:
360*0Sstevel@tonic-gate  *  pc      PathCache *  The cache to be deleted (can be NULL).
361*0Sstevel@tonic-gate  * Output:
362*0Sstevel@tonic-gate  *  return  PathCache *  The deleted object (ie. allways NULL).
363*0Sstevel@tonic-gate  */
del_PathCache(PathCache * pc)364*0Sstevel@tonic-gate PathCache *del_PathCache(PathCache *pc)
365*0Sstevel@tonic-gate {
366*0Sstevel@tonic-gate   if(pc) {
367*0Sstevel@tonic-gate /*
368*0Sstevel@tonic-gate  * Delete the error message buffer.
369*0Sstevel@tonic-gate  */
370*0Sstevel@tonic-gate     pc->err = _del_ErrMsg(pc->err);
371*0Sstevel@tonic-gate /*
372*0Sstevel@tonic-gate  * Delete the memory of the list of path nodes.
373*0Sstevel@tonic-gate  */
374*0Sstevel@tonic-gate     pc->node_mem = _del_FreeList(pc->node_mem, 1);
375*0Sstevel@tonic-gate /*
376*0Sstevel@tonic-gate  * Delete the memory used to record filenames.
377*0Sstevel@tonic-gate  */
378*0Sstevel@tonic-gate     pc->abs_mem = del_CacheMem(pc->abs_mem);
379*0Sstevel@tonic-gate     pc->rel_mem = del_CacheMem(pc->rel_mem);
380*0Sstevel@tonic-gate /*
381*0Sstevel@tonic-gate  * The list of PathNode's was already deleted when node_mem was
382*0Sstevel@tonic-gate  * deleted.
383*0Sstevel@tonic-gate  */
384*0Sstevel@tonic-gate     pc->head = NULL;
385*0Sstevel@tonic-gate     pc->tail = NULL;
386*0Sstevel@tonic-gate /*
387*0Sstevel@tonic-gate  * Delete the pathname buffer.
388*0Sstevel@tonic-gate  */
389*0Sstevel@tonic-gate     pc->path = _del_PathName(pc->path);
390*0Sstevel@tonic-gate /*
391*0Sstevel@tonic-gate  * Delete the home-directory lookup object.
392*0Sstevel@tonic-gate  */
393*0Sstevel@tonic-gate     pc->home = _del_HomeDir(pc->home);
394*0Sstevel@tonic-gate /*
395*0Sstevel@tonic-gate  * Delete the directory reader.
396*0Sstevel@tonic-gate  */
397*0Sstevel@tonic-gate     pc->dr = _del_DirReader(pc->dr);
398*0Sstevel@tonic-gate /*
399*0Sstevel@tonic-gate  * Delete the cpl_file_completions() config object.
400*0Sstevel@tonic-gate  */
401*0Sstevel@tonic-gate     pc->cfc = del_CplFileConf(pc->cfc);
402*0Sstevel@tonic-gate /*
403*0Sstevel@tonic-gate  * Delete the container.
404*0Sstevel@tonic-gate  */
405*0Sstevel@tonic-gate     free(pc);
406*0Sstevel@tonic-gate   };
407*0Sstevel@tonic-gate   return NULL;
408*0Sstevel@tonic-gate }
409*0Sstevel@tonic-gate 
410*0Sstevel@tonic-gate /*.......................................................................
411*0Sstevel@tonic-gate  * If you want subsequent calls to pca_lookup_file() and
412*0Sstevel@tonic-gate  * pca_path_completions() to only return the filenames of certain
413*0Sstevel@tonic-gate  * types of files, for example executables, or filenames ending in
414*0Sstevel@tonic-gate  * ".ps", call this function to register a file-selection callback
415*0Sstevel@tonic-gate  * function. This callback function takes the full pathname of a file,
416*0Sstevel@tonic-gate  * plus application-specific data, and returns 1 if the file is of
417*0Sstevel@tonic-gate  * interest, and zero otherwise.
418*0Sstevel@tonic-gate  *
419*0Sstevel@tonic-gate  * Input:
420*0Sstevel@tonic-gate  *  pc         PathCache *  The filename cache.
421*0Sstevel@tonic-gate  *  check_fn  CplCheckFn *  The function to call to see if the name of
422*0Sstevel@tonic-gate  *                          a given file should be included in the
423*0Sstevel@tonic-gate  *                          cache. This determines what type of files
424*0Sstevel@tonic-gate  *                          will reside in the cache. To revert to
425*0Sstevel@tonic-gate  *                          selecting all files, regardless of type,
426*0Sstevel@tonic-gate  *                          pass 0 here.
427*0Sstevel@tonic-gate  *  data            void *  You can pass a pointer to anything you
428*0Sstevel@tonic-gate  *                          like here, including NULL. It will be
429*0Sstevel@tonic-gate  *                          passed to your check_fn() callback
430*0Sstevel@tonic-gate  *                          function, for its private use.
431*0Sstevel@tonic-gate  */
pca_set_check_fn(PathCache * pc,CplCheckFn * check_fn,void * data)432*0Sstevel@tonic-gate void pca_set_check_fn(PathCache *pc, CplCheckFn *check_fn, void *data)
433*0Sstevel@tonic-gate {
434*0Sstevel@tonic-gate   if(pc) {
435*0Sstevel@tonic-gate /*
436*0Sstevel@tonic-gate  * If the callback or its data pointer have changed, clear the cached
437*0Sstevel@tonic-gate  * statuses of files that were accepted or rejected by the previous
438*0Sstevel@tonic-gate  * calback.
439*0Sstevel@tonic-gate  */
440*0Sstevel@tonic-gate     if(check_fn != pc->check_fn || data != pc->data)
441*0Sstevel@tonic-gate       pca_remove_marks(pc);
442*0Sstevel@tonic-gate /*
443*0Sstevel@tonic-gate  * Record the new callback locally.
444*0Sstevel@tonic-gate  */
445*0Sstevel@tonic-gate     pc->check_fn = check_fn;
446*0Sstevel@tonic-gate     pc->data = data;
447*0Sstevel@tonic-gate /*
448*0Sstevel@tonic-gate  * Configure cpl_file_completions() to use the same callback to
449*0Sstevel@tonic-gate  * select files of interest.
450*0Sstevel@tonic-gate  */
451*0Sstevel@tonic-gate     cfc_set_check_fn(pc->cfc, check_fn, data);
452*0Sstevel@tonic-gate   };
453*0Sstevel@tonic-gate   return;
454*0Sstevel@tonic-gate }
455*0Sstevel@tonic-gate 
456*0Sstevel@tonic-gate /*.......................................................................
457*0Sstevel@tonic-gate  * Return a description of the last path-caching error that occurred.
458*0Sstevel@tonic-gate  *
459*0Sstevel@tonic-gate  * Input:
460*0Sstevel@tonic-gate  *  pc     PathCache *   The filename cache that suffered the error.
461*0Sstevel@tonic-gate  * Output:
462*0Sstevel@tonic-gate  *  return      char *   The description of the last error.
463*0Sstevel@tonic-gate  */
pca_last_error(PathCache * pc)464*0Sstevel@tonic-gate const char *pca_last_error(PathCache *pc)
465*0Sstevel@tonic-gate {
466*0Sstevel@tonic-gate   return pc ? _err_get_msg(pc->err) : "NULL PathCache argument";
467*0Sstevel@tonic-gate }
468*0Sstevel@tonic-gate 
469*0Sstevel@tonic-gate /*.......................................................................
470*0Sstevel@tonic-gate  * Discard all cached filenames.
471*0Sstevel@tonic-gate  *
472*0Sstevel@tonic-gate  * Input:
473*0Sstevel@tonic-gate  *  pc   PathCache *  The cache to be cleared.
474*0Sstevel@tonic-gate  */
pca_clear_cache(PathCache * pc)475*0Sstevel@tonic-gate static void pca_clear_cache(PathCache *pc)
476*0Sstevel@tonic-gate {
477*0Sstevel@tonic-gate   if(pc) {
478*0Sstevel@tonic-gate /*
479*0Sstevel@tonic-gate  * Return all path-nodes to the freelist.
480*0Sstevel@tonic-gate  */
481*0Sstevel@tonic-gate     _rst_FreeList(pc->node_mem);
482*0Sstevel@tonic-gate     pc->head = pc->tail = NULL;
483*0Sstevel@tonic-gate /*
484*0Sstevel@tonic-gate  * Delete all filename strings.
485*0Sstevel@tonic-gate  */
486*0Sstevel@tonic-gate     rst_CacheMem(pc->abs_mem);
487*0Sstevel@tonic-gate     rst_CacheMem(pc->rel_mem);
488*0Sstevel@tonic-gate   };
489*0Sstevel@tonic-gate   return;
490*0Sstevel@tonic-gate }
491*0Sstevel@tonic-gate 
492*0Sstevel@tonic-gate /*.......................................................................
493*0Sstevel@tonic-gate  * Build the list of files of interest contained in a given
494*0Sstevel@tonic-gate  * colon-separated list of directories.
495*0Sstevel@tonic-gate  *
496*0Sstevel@tonic-gate  * Input:
497*0Sstevel@tonic-gate  *  pc         PathCache *  The cache in which to store the names of
498*0Sstevel@tonic-gate  *                          the files that are found in the list of
499*0Sstevel@tonic-gate  *                          directories.
500*0Sstevel@tonic-gate  *  path      const char *  A colon-separated list of directory
501*0Sstevel@tonic-gate  *                          paths. Under UNIX, when searching for
502*0Sstevel@tonic-gate  *                          executables, this should be the return
503*0Sstevel@tonic-gate  *                          value of getenv("PATH").
504*0Sstevel@tonic-gate  * Output:
505*0Sstevel@tonic-gate  *  return           int    0 - OK.
506*0Sstevel@tonic-gate  *                          1 - An error occurred. A description of
507*0Sstevel@tonic-gate  *                              the error can be acquired by calling
508*0Sstevel@tonic-gate  *                              pca_last_error(pc).
509*0Sstevel@tonic-gate  */
pca_scan_path(PathCache * pc,const char * path)510*0Sstevel@tonic-gate int pca_scan_path(PathCache *pc, const char *path)
511*0Sstevel@tonic-gate {
512*0Sstevel@tonic-gate   const char *pptr; /* A pointer to the next unprocessed character in path[] */
513*0Sstevel@tonic-gate   PathNode *node;   /* A node in the list of directory paths */
514*0Sstevel@tonic-gate   char **fptr;      /* A pointer into pc->abs_mem->files[] */
515*0Sstevel@tonic-gate /*
516*0Sstevel@tonic-gate  * Check the arguments.
517*0Sstevel@tonic-gate  */
518*0Sstevel@tonic-gate   if(!pc)
519*0Sstevel@tonic-gate     return 1;
520*0Sstevel@tonic-gate /*
521*0Sstevel@tonic-gate  * Clear the outdated contents of the cache.
522*0Sstevel@tonic-gate  */
523*0Sstevel@tonic-gate   pca_clear_cache(pc);
524*0Sstevel@tonic-gate /*
525*0Sstevel@tonic-gate  * If no path list was provided, there is nothing to be added to the
526*0Sstevel@tonic-gate  * cache.
527*0Sstevel@tonic-gate  */
528*0Sstevel@tonic-gate   if(!path)
529*0Sstevel@tonic-gate     return 0;
530*0Sstevel@tonic-gate /*
531*0Sstevel@tonic-gate  * Extract directories from the path list, expanding tilde expressions
532*0Sstevel@tonic-gate  * on the fly into pc->pathname, then add them to the list of path
533*0Sstevel@tonic-gate  * nodes, along with a sorted list of the filenames of interest that
534*0Sstevel@tonic-gate  * the directories hold.
535*0Sstevel@tonic-gate  */
536*0Sstevel@tonic-gate   pptr = path;
537*0Sstevel@tonic-gate   while(*pptr) {
538*0Sstevel@tonic-gate /*
539*0Sstevel@tonic-gate  * Extract the next pathname component into pc->path->name.
540*0Sstevel@tonic-gate  */
541*0Sstevel@tonic-gate     if(pca_extract_dir(pc, pptr, &pptr))
542*0Sstevel@tonic-gate       return 1;
543*0Sstevel@tonic-gate /*
544*0Sstevel@tonic-gate  * Add a new node to the list of paths, containing both the
545*0Sstevel@tonic-gate  * directory name and, if not a relative pathname, the list of
546*0Sstevel@tonic-gate  * files of interest in the directory.
547*0Sstevel@tonic-gate  */
548*0Sstevel@tonic-gate     if(add_PathNode(pc, pc->path->name))
549*0Sstevel@tonic-gate       return 1;
550*0Sstevel@tonic-gate   };
551*0Sstevel@tonic-gate /*
552*0Sstevel@tonic-gate  * The file arrays in each absolute directory node are sections of
553*0Sstevel@tonic-gate  * pc->abs_mem->files[]. Record pointers to the starts of each
554*0Sstevel@tonic-gate  * of these sections in each directory node. Note that this couldn't
555*0Sstevel@tonic-gate  * be done in add_PathNode(), because pc->abs_mem->files[] may
556*0Sstevel@tonic-gate  * get reallocated in subsequent calls to add_PathNode(), thus
557*0Sstevel@tonic-gate  * invalidating any pointers to it.
558*0Sstevel@tonic-gate  */
559*0Sstevel@tonic-gate   fptr = pc->abs_mem->files;
560*0Sstevel@tonic-gate   for(node=pc->head; node; node=node->next) {
561*0Sstevel@tonic-gate     node->files = fptr;
562*0Sstevel@tonic-gate     fptr += node->nfile;
563*0Sstevel@tonic-gate   };
564*0Sstevel@tonic-gate   return 0;
565*0Sstevel@tonic-gate }
566*0Sstevel@tonic-gate 
567*0Sstevel@tonic-gate /*.......................................................................
568*0Sstevel@tonic-gate  * Extract the next directory path from a colon-separated list of
569*0Sstevel@tonic-gate  * directories, expanding tilde home-directory expressions where needed.
570*0Sstevel@tonic-gate  *
571*0Sstevel@tonic-gate  * Input:
572*0Sstevel@tonic-gate  *  pc      PathCache *   The cache of filenames.
573*0Sstevel@tonic-gate  *  path   const char *   A pointer to the start of the next component
574*0Sstevel@tonic-gate  *                        in the path list.
575*0Sstevel@tonic-gate  * Input/Output:
576*0Sstevel@tonic-gate  *  nextp  const char **  A pointer to the next unprocessed character
577*0Sstevel@tonic-gate  *                        in path[] will be assigned to *nextp.
578*0Sstevel@tonic-gate  * Output:
579*0Sstevel@tonic-gate  *  return        int     0 - OK. The extracted path is in pc->path->name.
580*0Sstevel@tonic-gate  *                        1 - Error. A description of the error will
581*0Sstevel@tonic-gate  *                            have been left in pc->err.
582*0Sstevel@tonic-gate  */
pca_extract_dir(PathCache * pc,const char * path,const char ** nextp)583*0Sstevel@tonic-gate static int pca_extract_dir(PathCache *pc, const char *path, const char **nextp)
584*0Sstevel@tonic-gate {
585*0Sstevel@tonic-gate   const char *pptr;         /* A pointer into path[] */
586*0Sstevel@tonic-gate   const char *sptr;         /* The path following tilde expansion */
587*0Sstevel@tonic-gate   int escaped = 0;          /* True if the last character was a backslash */
588*0Sstevel@tonic-gate /*
589*0Sstevel@tonic-gate  * If there is a tilde expression at the beginning of the specified path,
590*0Sstevel@tonic-gate  * place the corresponding home directory into pc->path. Otherwise
591*0Sstevel@tonic-gate  * just clear pc->path.
592*0Sstevel@tonic-gate  */
593*0Sstevel@tonic-gate   if(pca_expand_tilde(pc, path, strlen(path), 0, &pptr))
594*0Sstevel@tonic-gate     return 1;
595*0Sstevel@tonic-gate /*
596*0Sstevel@tonic-gate  * Keep a record of the current location in the path.
597*0Sstevel@tonic-gate  */
598*0Sstevel@tonic-gate   sptr = pptr;
599*0Sstevel@tonic-gate /*
600*0Sstevel@tonic-gate  * Locate the end of the directory name in the pathname string, stopping
601*0Sstevel@tonic-gate  * when either the end of the string is reached, or an un-escaped colon
602*0Sstevel@tonic-gate  * separator is seen.
603*0Sstevel@tonic-gate  */
604*0Sstevel@tonic-gate   while(*pptr && (escaped || *pptr != ':'))
605*0Sstevel@tonic-gate     escaped = !escaped && *pptr++ == '\\';
606*0Sstevel@tonic-gate /*
607*0Sstevel@tonic-gate  * Append the rest of the directory path to the pathname buffer.
608*0Sstevel@tonic-gate  */
609*0Sstevel@tonic-gate   if(_pn_append_to_path(pc->path, sptr, pptr - sptr, 1) == NULL) {
610*0Sstevel@tonic-gate     _err_record_msg(pc->err, "Insufficient memory to record directory name",
611*0Sstevel@tonic-gate 		    END_ERR_MSG);
612*0Sstevel@tonic-gate     return 1;
613*0Sstevel@tonic-gate   };
614*0Sstevel@tonic-gate /*
615*0Sstevel@tonic-gate  * To facilitate subsequently appending filenames to the directory
616*0Sstevel@tonic-gate  * path name, make sure that the recorded directory name ends in a
617*0Sstevel@tonic-gate  * directory separator.
618*0Sstevel@tonic-gate  */
619*0Sstevel@tonic-gate   {
620*0Sstevel@tonic-gate     int dirlen = strlen(pc->path->name);
621*0Sstevel@tonic-gate     if(dirlen < FS_DIR_SEP_LEN ||
622*0Sstevel@tonic-gate        strncmp(pc->path->name + dirlen - FS_DIR_SEP_LEN, FS_DIR_SEP,
623*0Sstevel@tonic-gate 	       FS_DIR_SEP_LEN) != 0) {
624*0Sstevel@tonic-gate       if(_pn_append_to_path(pc->path, FS_DIR_SEP, FS_DIR_SEP_LEN, 0) == NULL) {
625*0Sstevel@tonic-gate 	_err_record_msg(pc->err, "Insufficient memory to record directory name",
626*0Sstevel@tonic-gate 			END_ERR_MSG);
627*0Sstevel@tonic-gate 	return 1;
628*0Sstevel@tonic-gate       };
629*0Sstevel@tonic-gate     };
630*0Sstevel@tonic-gate   };
631*0Sstevel@tonic-gate /*
632*0Sstevel@tonic-gate  * Skip the separator unless we have reached the end of the path.
633*0Sstevel@tonic-gate  */
634*0Sstevel@tonic-gate   if(*pptr==':')
635*0Sstevel@tonic-gate     pptr++;
636*0Sstevel@tonic-gate /*
637*0Sstevel@tonic-gate  * Return the unprocessed tail of the path-list string.
638*0Sstevel@tonic-gate  */
639*0Sstevel@tonic-gate   *nextp = pptr;
640*0Sstevel@tonic-gate   return 0;
641*0Sstevel@tonic-gate }
642*0Sstevel@tonic-gate 
643*0Sstevel@tonic-gate /*.......................................................................
644*0Sstevel@tonic-gate  * Read a username, stopping when a directory separator is seen, a colon
645*0Sstevel@tonic-gate  * separator is seen, the end of the string is reached, or the username
646*0Sstevel@tonic-gate  * buffer overflows.
647*0Sstevel@tonic-gate  *
648*0Sstevel@tonic-gate  * Input:
649*0Sstevel@tonic-gate  *  pc   PathCache *   The cache of filenames.
650*0Sstevel@tonic-gate  *  string    char *   The string who's prefix contains the name.
651*0Sstevel@tonic-gate  *  slen       int     The max number of characters to read from string[].
652*0Sstevel@tonic-gate  *  literal    int     If true, treat backslashes as literal characters
653*0Sstevel@tonic-gate  *                     instead of escapes.
654*0Sstevel@tonic-gate  * Input/Output:
655*0Sstevel@tonic-gate  *  nextp     char **  A pointer to the next unprocessed character
656*0Sstevel@tonic-gate  *                     in string[] will be assigned to *nextp.
657*0Sstevel@tonic-gate  * Output:
658*0Sstevel@tonic-gate  *  return     int     0 - OK. The username can be found in pc->usrnam.
659*0Sstevel@tonic-gate  *                     1 - Error. A description of the error message
660*0Sstevel@tonic-gate  *                         can be found in pc->err.
661*0Sstevel@tonic-gate  */
pca_read_username(PathCache * pc,const char * string,int slen,int literal,const char ** nextp)662*0Sstevel@tonic-gate static int pca_read_username(PathCache *pc, const char *string, int slen,
663*0Sstevel@tonic-gate 			     int literal, const char **nextp)
664*0Sstevel@tonic-gate {
665*0Sstevel@tonic-gate   int usrlen;         /* The number of characters in pc->usrnam[] */
666*0Sstevel@tonic-gate   const char *sptr;   /* A pointer into string[] */
667*0Sstevel@tonic-gate   int escaped = 0;    /* True if the last character was a backslash */
668*0Sstevel@tonic-gate /*
669*0Sstevel@tonic-gate  * Extract the username.
670*0Sstevel@tonic-gate  */
671*0Sstevel@tonic-gate   for(sptr=string,usrlen=0; usrlen < USR_LEN && (sptr-string) < slen; sptr++) {
672*0Sstevel@tonic-gate /*
673*0Sstevel@tonic-gate  * Stop if the end of the string is reached, or a directory separator
674*0Sstevel@tonic-gate  * or un-escaped colon separator is seen.
675*0Sstevel@tonic-gate  */
676*0Sstevel@tonic-gate     if(!*sptr || strncmp(sptr, FS_DIR_SEP, FS_DIR_SEP_LEN)==0 ||
677*0Sstevel@tonic-gate        (!escaped && *sptr == ':'))
678*0Sstevel@tonic-gate       break;
679*0Sstevel@tonic-gate /*
680*0Sstevel@tonic-gate  * Escape the next character?
681*0Sstevel@tonic-gate  */
682*0Sstevel@tonic-gate     if(!literal && !escaped && *sptr == '\\') {
683*0Sstevel@tonic-gate       escaped = 1;
684*0Sstevel@tonic-gate     } else {
685*0Sstevel@tonic-gate       escaped = 0;
686*0Sstevel@tonic-gate       pc->usrnam[usrlen++] = *sptr;
687*0Sstevel@tonic-gate     };
688*0Sstevel@tonic-gate   };
689*0Sstevel@tonic-gate /*
690*0Sstevel@tonic-gate  * Did the username overflow the buffer?
691*0Sstevel@tonic-gate  */
692*0Sstevel@tonic-gate   if(usrlen >= USR_LEN) {
693*0Sstevel@tonic-gate     _err_record_msg(pc->err, "Username too long", END_ERR_MSG);
694*0Sstevel@tonic-gate     return 1;
695*0Sstevel@tonic-gate   };
696*0Sstevel@tonic-gate /*
697*0Sstevel@tonic-gate  * Terminate the string.
698*0Sstevel@tonic-gate  */
699*0Sstevel@tonic-gate   pc->usrnam[usrlen] = '\0';
700*0Sstevel@tonic-gate /*
701*0Sstevel@tonic-gate  * Indicate where processing of the input string should continue.
702*0Sstevel@tonic-gate  */
703*0Sstevel@tonic-gate   *nextp = sptr;
704*0Sstevel@tonic-gate   return 0;
705*0Sstevel@tonic-gate }
706*0Sstevel@tonic-gate 
707*0Sstevel@tonic-gate 
708*0Sstevel@tonic-gate /*.......................................................................
709*0Sstevel@tonic-gate  * Create a new CacheMem object.
710*0Sstevel@tonic-gate  *
711*0Sstevel@tonic-gate  * Output:
712*0Sstevel@tonic-gate  *  return  CacheMem *  The new object, or NULL on error.
713*0Sstevel@tonic-gate  */
new_CacheMem(void)714*0Sstevel@tonic-gate static CacheMem *new_CacheMem(void)
715*0Sstevel@tonic-gate {
716*0Sstevel@tonic-gate   CacheMem *cm;  /* The object to be returned */
717*0Sstevel@tonic-gate /*
718*0Sstevel@tonic-gate  * Allocate the container.
719*0Sstevel@tonic-gate  */
720*0Sstevel@tonic-gate   cm = (CacheMem *)malloc(sizeof(CacheMem));
721*0Sstevel@tonic-gate   if(!cm) {
722*0Sstevel@tonic-gate     errno = ENOMEM;
723*0Sstevel@tonic-gate     return NULL;
724*0Sstevel@tonic-gate   };
725*0Sstevel@tonic-gate /*
726*0Sstevel@tonic-gate  * Before attempting any operation that might fail, initialize the
727*0Sstevel@tonic-gate  * container at least up to the point at which it can safely be passed
728*0Sstevel@tonic-gate  * to del_CacheMem().
729*0Sstevel@tonic-gate  */
730*0Sstevel@tonic-gate   cm->sg = NULL;
731*0Sstevel@tonic-gate   cm->files_dim = 0;
732*0Sstevel@tonic-gate   cm->files = NULL;
733*0Sstevel@tonic-gate   cm->nfiles = 0;
734*0Sstevel@tonic-gate /*
735*0Sstevel@tonic-gate  * Allocate a list of string segments for storing filenames.
736*0Sstevel@tonic-gate  */
737*0Sstevel@tonic-gate   cm->sg = _new_StringGroup(_pu_pathname_dim());
738*0Sstevel@tonic-gate   if(!cm->sg)
739*0Sstevel@tonic-gate     return del_CacheMem(cm);
740*0Sstevel@tonic-gate /*
741*0Sstevel@tonic-gate  * Allocate an array of pointers to filenames.
742*0Sstevel@tonic-gate  * This will be extended later if needed.
743*0Sstevel@tonic-gate  */
744*0Sstevel@tonic-gate   cm->files_dim = FILES_BLK_FACT;
745*0Sstevel@tonic-gate   cm->files = (char **) malloc(sizeof(*cm->files) * cm->files_dim);
746*0Sstevel@tonic-gate   if(!cm->files) {
747*0Sstevel@tonic-gate     errno = ENOMEM;
748*0Sstevel@tonic-gate     return del_CacheMem(cm);
749*0Sstevel@tonic-gate   };
750*0Sstevel@tonic-gate   return cm;
751*0Sstevel@tonic-gate }
752*0Sstevel@tonic-gate 
753*0Sstevel@tonic-gate /*.......................................................................
754*0Sstevel@tonic-gate  * Delete a CacheMem object.
755*0Sstevel@tonic-gate  *
756*0Sstevel@tonic-gate  * Input:
757*0Sstevel@tonic-gate  *  cm   CacheMem *  The object to be deleted.
758*0Sstevel@tonic-gate  * Output:
759*0Sstevel@tonic-gate  *  return CacheMem *  The deleted object (always NULL).
760*0Sstevel@tonic-gate  */
del_CacheMem(CacheMem * cm)761*0Sstevel@tonic-gate static CacheMem *del_CacheMem(CacheMem *cm)
762*0Sstevel@tonic-gate {
763*0Sstevel@tonic-gate   if(cm) {
764*0Sstevel@tonic-gate /*
765*0Sstevel@tonic-gate  * Delete the memory that was used to record filename strings.
766*0Sstevel@tonic-gate  */
767*0Sstevel@tonic-gate     cm->sg = _del_StringGroup(cm->sg);
768*0Sstevel@tonic-gate /*
769*0Sstevel@tonic-gate  * Delete the array of pointers to filenames.
770*0Sstevel@tonic-gate  */
771*0Sstevel@tonic-gate     cm->files_dim = 0;
772*0Sstevel@tonic-gate     if(cm->files) {
773*0Sstevel@tonic-gate       free(cm->files);
774*0Sstevel@tonic-gate       cm->files = NULL;
775*0Sstevel@tonic-gate     };
776*0Sstevel@tonic-gate /*
777*0Sstevel@tonic-gate  * Delete the container.
778*0Sstevel@tonic-gate  */
779*0Sstevel@tonic-gate     free(cm);
780*0Sstevel@tonic-gate   };
781*0Sstevel@tonic-gate   return NULL;
782*0Sstevel@tonic-gate }
783*0Sstevel@tonic-gate 
784*0Sstevel@tonic-gate /*.......................................................................
785*0Sstevel@tonic-gate  * Re-initialize the memory used to allocate filename strings.
786*0Sstevel@tonic-gate  *
787*0Sstevel@tonic-gate  * Input:
788*0Sstevel@tonic-gate  *  cm     CacheMem *  The memory cache to be cleared.
789*0Sstevel@tonic-gate  */
rst_CacheMem(CacheMem * cm)790*0Sstevel@tonic-gate static void rst_CacheMem(CacheMem *cm)
791*0Sstevel@tonic-gate {
792*0Sstevel@tonic-gate   _clr_StringGroup(cm->sg);
793*0Sstevel@tonic-gate   cm->nfiles = 0;
794*0Sstevel@tonic-gate   return;
795*0Sstevel@tonic-gate }
796*0Sstevel@tonic-gate 
797*0Sstevel@tonic-gate /*.......................................................................
798*0Sstevel@tonic-gate  * Append a new directory node to the list of directories read from the
799*0Sstevel@tonic-gate  * path.
800*0Sstevel@tonic-gate  *
801*0Sstevel@tonic-gate  * Input:
802*0Sstevel@tonic-gate  *  pc        PathCache *  The filename cache.
803*0Sstevel@tonic-gate  *  dirname  const char *  The name of the new directory.
804*0Sstevel@tonic-gate  * Output:
805*0Sstevel@tonic-gate  *  return          int    0 - OK.
806*0Sstevel@tonic-gate  *                         1 - Error.
807*0Sstevel@tonic-gate  */
add_PathNode(PathCache * pc,const char * dirname)808*0Sstevel@tonic-gate static int add_PathNode(PathCache *pc, const char *dirname)
809*0Sstevel@tonic-gate {
810*0Sstevel@tonic-gate   PathNode *node;  /* The new directory list node */
811*0Sstevel@tonic-gate   int relative;    /* True if dirname[] is a relative pathname */
812*0Sstevel@tonic-gate /*
813*0Sstevel@tonic-gate  * Have we been passed a relative pathname or an absolute pathname?
814*0Sstevel@tonic-gate  */
815*0Sstevel@tonic-gate   relative = strncmp(dirname, FS_ROOT_DIR, FS_ROOT_DIR_LEN) != 0;
816*0Sstevel@tonic-gate /*
817*0Sstevel@tonic-gate  * If it's an absolute pathname, ignore it if the corresponding
818*0Sstevel@tonic-gate  * directory doesn't exist.
819*0Sstevel@tonic-gate  */
820*0Sstevel@tonic-gate   if(!relative && !_pu_path_is_dir(dirname))
821*0Sstevel@tonic-gate     return 0;
822*0Sstevel@tonic-gate /*
823*0Sstevel@tonic-gate  * Allocate a new list node to record the specifics of the new directory.
824*0Sstevel@tonic-gate  */
825*0Sstevel@tonic-gate   node = (PathNode *) _new_FreeListNode(pc->node_mem);
826*0Sstevel@tonic-gate   if(!node) {
827*0Sstevel@tonic-gate     _err_record_msg(pc->err, "Insufficient memory to cache new directory.",
828*0Sstevel@tonic-gate 		    END_ERR_MSG);
829*0Sstevel@tonic-gate     return 1;
830*0Sstevel@tonic-gate   };
831*0Sstevel@tonic-gate /*
832*0Sstevel@tonic-gate  * Initialize the node.
833*0Sstevel@tonic-gate  */
834*0Sstevel@tonic-gate   node->next = NULL;
835*0Sstevel@tonic-gate   node->relative = relative;
836*0Sstevel@tonic-gate   node->mem = relative ? pc->rel_mem : pc->abs_mem;
837*0Sstevel@tonic-gate   node->dir = NULL;
838*0Sstevel@tonic-gate   node->nfile = 0;
839*0Sstevel@tonic-gate   node->files = NULL;
840*0Sstevel@tonic-gate /*
841*0Sstevel@tonic-gate  * Make a copy of the directory pathname.
842*0Sstevel@tonic-gate  */
843*0Sstevel@tonic-gate   node->dir = _sg_store_string(pc->abs_mem->sg, dirname, 0);
844*0Sstevel@tonic-gate   if(!node->dir) {
845*0Sstevel@tonic-gate     _err_record_msg(pc->err, "Insufficient memory to store directory name.",
846*0Sstevel@tonic-gate 		    END_ERR_MSG);
847*0Sstevel@tonic-gate     return 1;
848*0Sstevel@tonic-gate   };
849*0Sstevel@tonic-gate /*
850*0Sstevel@tonic-gate  * Scan absolute directories for files of interest, recording their names
851*0Sstevel@tonic-gate  * in node->mem->sg and appending pointers to these names to the
852*0Sstevel@tonic-gate  * node->mem->files[] array.
853*0Sstevel@tonic-gate  */
854*0Sstevel@tonic-gate   if(!node->relative) {
855*0Sstevel@tonic-gate     int nfile = node->nfile = pca_scan_dir(pc, node->dir, node->mem);
856*0Sstevel@tonic-gate     if(nfile < 1) {  /* No files matched or an error occurred */
857*0Sstevel@tonic-gate       node = (PathNode *) _del_FreeListNode(pc->node_mem, node);
858*0Sstevel@tonic-gate       return nfile < 0;
859*0Sstevel@tonic-gate     };
860*0Sstevel@tonic-gate   };
861*0Sstevel@tonic-gate /*
862*0Sstevel@tonic-gate  * Append the new node to the list.
863*0Sstevel@tonic-gate  */
864*0Sstevel@tonic-gate   if(pc->head) {
865*0Sstevel@tonic-gate     pc->tail->next = node;
866*0Sstevel@tonic-gate     pc->tail = node;
867*0Sstevel@tonic-gate   } else {
868*0Sstevel@tonic-gate     pc->head = pc->tail = node;
869*0Sstevel@tonic-gate   };
870*0Sstevel@tonic-gate   return 0;
871*0Sstevel@tonic-gate }
872*0Sstevel@tonic-gate 
873*0Sstevel@tonic-gate /*.......................................................................
874*0Sstevel@tonic-gate  * Scan a given directory for files of interest, record their names
875*0Sstevel@tonic-gate  * in mem->sg and append pointers to them to the mem->files[] array.
876*0Sstevel@tonic-gate  *
877*0Sstevel@tonic-gate  * Input:
878*0Sstevel@tonic-gate  *  pc        PathCache *  The filename cache.
879*0Sstevel@tonic-gate  *  dirname  const char *  The pathname of the directory to be scanned.
880*0Sstevel@tonic-gate  *  mem        CacheMem *  The memory in which to store filenames of
881*0Sstevel@tonic-gate  *                         interest.
882*0Sstevel@tonic-gate  * Output:
883*0Sstevel@tonic-gate  *  return          int    The number of files recorded, or -1 if a
884*0Sstevel@tonic-gate  *                         memory error occurs. Note that the
885*0Sstevel@tonic-gate  *                         inability to read the contents of the
886*0Sstevel@tonic-gate  *                         directory is not counted as an error.
887*0Sstevel@tonic-gate  */
pca_scan_dir(PathCache * pc,const char * dirname,CacheMem * mem)888*0Sstevel@tonic-gate static int pca_scan_dir(PathCache *pc, const char *dirname, CacheMem *mem)
889*0Sstevel@tonic-gate {
890*0Sstevel@tonic-gate   int nfile = 0;        /* The number of filenames recorded */
891*0Sstevel@tonic-gate   const char *filename; /* The name of the file being looked at */
892*0Sstevel@tonic-gate /*
893*0Sstevel@tonic-gate  * Attempt to open the directory. If the directory can't be read then
894*0Sstevel@tonic-gate  * there are no accessible files of interest in the directory.
895*0Sstevel@tonic-gate  */
896*0Sstevel@tonic-gate   if(_dr_open_dir(pc->dr, dirname, NULL))
897*0Sstevel@tonic-gate     return 0;
898*0Sstevel@tonic-gate /*
899*0Sstevel@tonic-gate  * Record the names of all files in the directory in the cache.
900*0Sstevel@tonic-gate  */
901*0Sstevel@tonic-gate   while((filename = _dr_next_file(pc->dr))) {
902*0Sstevel@tonic-gate     char *copy;        /* A copy of the filename */
903*0Sstevel@tonic-gate /*
904*0Sstevel@tonic-gate  * Make a temporary copy of the filename with an extra byte prepended.
905*0Sstevel@tonic-gate  */
906*0Sstevel@tonic-gate     _pn_clear_path(pc->path);
907*0Sstevel@tonic-gate     if(_pn_append_to_path(pc->path, " ", 1, 0) == NULL ||
908*0Sstevel@tonic-gate        _pn_append_to_path(pc->path, filename, -1, 1) == NULL) {
909*0Sstevel@tonic-gate       _err_record_msg(pc->err, "Insufficient memory to record filename",
910*0Sstevel@tonic-gate 		      END_ERR_MSG);
911*0Sstevel@tonic-gate       return -1;
912*0Sstevel@tonic-gate     };
913*0Sstevel@tonic-gate /*
914*0Sstevel@tonic-gate  * Store the filename.
915*0Sstevel@tonic-gate  */
916*0Sstevel@tonic-gate     copy = _sg_store_string(mem->sg, pc->path->name, 0);
917*0Sstevel@tonic-gate     if(!copy) {
918*0Sstevel@tonic-gate       _err_record_msg(pc->err, "Insufficient memory to cache file name.",
919*0Sstevel@tonic-gate 		      END_ERR_MSG);
920*0Sstevel@tonic-gate       return -1;
921*0Sstevel@tonic-gate     };
922*0Sstevel@tonic-gate /*
923*0Sstevel@tonic-gate  * Mark the filename as unchecked.
924*0Sstevel@tonic-gate  */
925*0Sstevel@tonic-gate     copy[0] = PCA_F_ENIGMA;
926*0Sstevel@tonic-gate /*
927*0Sstevel@tonic-gate  * Make room to store a pointer to the copy in mem->files[].
928*0Sstevel@tonic-gate  */
929*0Sstevel@tonic-gate     if(mem->nfiles + 1 > mem->files_dim) {
930*0Sstevel@tonic-gate       int needed = mem->files_dim + FILES_BLK_FACT;
931*0Sstevel@tonic-gate       char **files = (char **) realloc(mem->files, sizeof(*mem->files)*needed);
932*0Sstevel@tonic-gate       if(!files) {
933*0Sstevel@tonic-gate 	_err_record_msg(pc->err,
934*0Sstevel@tonic-gate 			"Insufficient memory to extend filename cache.",
935*0Sstevel@tonic-gate 			END_ERR_MSG);
936*0Sstevel@tonic-gate 	return 1;
937*0Sstevel@tonic-gate       };
938*0Sstevel@tonic-gate       mem->files = files;
939*0Sstevel@tonic-gate       mem->files_dim = needed;
940*0Sstevel@tonic-gate     };
941*0Sstevel@tonic-gate /*
942*0Sstevel@tonic-gate  * Record a pointer to the copy of the filename at the end of the files[]
943*0Sstevel@tonic-gate  * array.
944*0Sstevel@tonic-gate  */
945*0Sstevel@tonic-gate     mem->files[mem->nfiles++] = copy;
946*0Sstevel@tonic-gate /*
947*0Sstevel@tonic-gate  * Keep a record of the number of files matched so far.
948*0Sstevel@tonic-gate  */
949*0Sstevel@tonic-gate     nfile++;
950*0Sstevel@tonic-gate   };
951*0Sstevel@tonic-gate /*
952*0Sstevel@tonic-gate  * Sort the list of files into lexical order.
953*0Sstevel@tonic-gate  */
954*0Sstevel@tonic-gate   qsort(mem->files + mem->nfiles - nfile, nfile, sizeof(*mem->files),
955*0Sstevel@tonic-gate 	pca_cmp_matches);
956*0Sstevel@tonic-gate /*
957*0Sstevel@tonic-gate  * Return the number of files recorded in mem->files[].
958*0Sstevel@tonic-gate  */
959*0Sstevel@tonic-gate   return nfile;
960*0Sstevel@tonic-gate }
961*0Sstevel@tonic-gate 
962*0Sstevel@tonic-gate /*.......................................................................
963*0Sstevel@tonic-gate  * A qsort() comparison function for comparing the cached filename
964*0Sstevel@tonic-gate  * strings pointed to by two (char **) array elements. Note that
965*0Sstevel@tonic-gate  * this ignores the initial cache-status byte of each filename.
966*0Sstevel@tonic-gate  *
967*0Sstevel@tonic-gate  * Input:
968*0Sstevel@tonic-gate  *  v1, v2   void *  Pointers to the pointers of two strings to be compared.
969*0Sstevel@tonic-gate  * Output:
970*0Sstevel@tonic-gate  *  return    int    -1 -> v1 < v2.
971*0Sstevel@tonic-gate  *                    0 -> v1 == v2
972*0Sstevel@tonic-gate  *                    1 -> v1 > v2
973*0Sstevel@tonic-gate  */
pca_cmp_matches(const void * v1,const void * v2)974*0Sstevel@tonic-gate static int pca_cmp_matches(const void *v1, const void *v2)
975*0Sstevel@tonic-gate {
976*0Sstevel@tonic-gate   const char **s1 = (const char **) v1;
977*0Sstevel@tonic-gate   const char **s2 = (const char **) v2;
978*0Sstevel@tonic-gate   return strcmp(*s1+1, *s2+1);
979*0Sstevel@tonic-gate }
980*0Sstevel@tonic-gate 
981*0Sstevel@tonic-gate /*.......................................................................
982*0Sstevel@tonic-gate  * Given the simple name of a file, search the cached list of files
983*0Sstevel@tonic-gate  * in the order in which they where found in the list of directories
984*0Sstevel@tonic-gate  * previously presented to pca_scan_path(), and return the pathname
985*0Sstevel@tonic-gate  * of the first file which has this name. If a pathname to a file is
986*0Sstevel@tonic-gate  * given instead of a simple filename, this is returned without being
987*0Sstevel@tonic-gate  * looked up in the cache, but with any initial ~username expression
988*0Sstevel@tonic-gate  * expanded, and optionally, unescaped backslashes removed.
989*0Sstevel@tonic-gate  *
990*0Sstevel@tonic-gate  * Input:
991*0Sstevel@tonic-gate  *  pc     PathCache *  The cached list of files.
992*0Sstevel@tonic-gate  *  name  const char *  The name of the file to lookup.
993*0Sstevel@tonic-gate  *  name_len     int    The length of the filename string at the
994*0Sstevel@tonic-gate  *                      beginning of name[], or -1 to indicate that
995*0Sstevel@tonic-gate  *                      the filename occupies the whole of the
996*0Sstevel@tonic-gate  *                      string.
997*0Sstevel@tonic-gate  *  literal      int    If this argument is zero, lone backslashes
998*0Sstevel@tonic-gate  *                      in name[] are ignored during comparison
999*0Sstevel@tonic-gate  *                      with filenames in the cache, under the
1000*0Sstevel@tonic-gate  *                      assumption that they were in the input line
1001*0Sstevel@tonic-gate  *                      soley to escape the special significance of
1002*0Sstevel@tonic-gate  *                      characters like spaces. To have them treated
1003*0Sstevel@tonic-gate  *                      as normal characters, give this argument a
1004*0Sstevel@tonic-gate  *                      non-zero value, such as 1.
1005*0Sstevel@tonic-gate  * Output:
1006*0Sstevel@tonic-gate  *  return      char *  The pathname of the first matching file,
1007*0Sstevel@tonic-gate  *                      or NULL if not found. Note that the returned
1008*0Sstevel@tonic-gate  *                      pointer points to memory owned by *pc, and
1009*0Sstevel@tonic-gate  *                      will become invalid on the next call to any
1010*0Sstevel@tonic-gate  *                      function in the PathCache module.
1011*0Sstevel@tonic-gate  */
pca_lookup_file(PathCache * pc,const char * name,int name_len,int literal)1012*0Sstevel@tonic-gate char *pca_lookup_file(PathCache *pc, const char *name, int name_len,
1013*0Sstevel@tonic-gate 		      int literal)
1014*0Sstevel@tonic-gate {
1015*0Sstevel@tonic-gate   PathNode *node;   /* A node in the list of directories in the path */
1016*0Sstevel@tonic-gate   char **match;     /* A pointer to a matching filename string in the cache */
1017*0Sstevel@tonic-gate /*
1018*0Sstevel@tonic-gate  * Check the arguments.
1019*0Sstevel@tonic-gate  */
1020*0Sstevel@tonic-gate   if(!pc || !name || name_len==0)
1021*0Sstevel@tonic-gate     return NULL;
1022*0Sstevel@tonic-gate /*
1023*0Sstevel@tonic-gate  * If no length was specified, determine the length of the string to
1024*0Sstevel@tonic-gate  * be looked up.
1025*0Sstevel@tonic-gate  */
1026*0Sstevel@tonic-gate   if(name_len < 0)
1027*0Sstevel@tonic-gate     name_len = strlen(name);
1028*0Sstevel@tonic-gate /*
1029*0Sstevel@tonic-gate  * If the word starts with a ~username expression, the root directory,
1030*0Sstevel@tonic-gate  * of it contains any directory separators, then treat it isn't a simple
1031*0Sstevel@tonic-gate  * filename that can be looked up in the cache, but rather appears to
1032*0Sstevel@tonic-gate  * be the pathname of a file. If so, return a copy of this pathname with
1033*0Sstevel@tonic-gate  * escapes removed, if requested, and any initial ~username expression
1034*0Sstevel@tonic-gate  * expanded.
1035*0Sstevel@tonic-gate  */
1036*0Sstevel@tonic-gate   if(cpa_cmd_contains_path(name, name_len)) {
1037*0Sstevel@tonic-gate     const char *nptr;
1038*0Sstevel@tonic-gate     if(pca_expand_tilde(pc, name, name_len, literal, &nptr) ||
1039*0Sstevel@tonic-gate        _pn_append_to_path(pc->path, nptr, name_len - (nptr-name),
1040*0Sstevel@tonic-gate 			  !literal) == NULL)
1041*0Sstevel@tonic-gate       return NULL;
1042*0Sstevel@tonic-gate     return pc->path->name;
1043*0Sstevel@tonic-gate   };
1044*0Sstevel@tonic-gate /*
1045*0Sstevel@tonic-gate  * Look up the specified filename in each of the directories of the path,
1046*0Sstevel@tonic-gate  * in the same order that they were listed in the path, and stop as soon
1047*0Sstevel@tonic-gate  * as an instance of the file is found.
1048*0Sstevel@tonic-gate  */
1049*0Sstevel@tonic-gate   for(node=pc->head; node; node=node->next) {
1050*0Sstevel@tonic-gate /*
1051*0Sstevel@tonic-gate  * If the directory of the latest node is a relative pathname,
1052*0Sstevel@tonic-gate  * scan it for files of interest.
1053*0Sstevel@tonic-gate  */
1054*0Sstevel@tonic-gate     if(node->relative) {
1055*0Sstevel@tonic-gate       rst_CacheMem(node->mem);
1056*0Sstevel@tonic-gate       if(pca_scan_dir(pc, node->dir, node->mem) < 1)
1057*0Sstevel@tonic-gate 	continue;
1058*0Sstevel@tonic-gate       node->files = node->mem->files;
1059*0Sstevel@tonic-gate       node->nfile = node->mem->nfiles;
1060*0Sstevel@tonic-gate     };
1061*0Sstevel@tonic-gate /*
1062*0Sstevel@tonic-gate  * Copy the filename into a temporary buffer, while interpretting
1063*0Sstevel@tonic-gate  * escape characters if needed.
1064*0Sstevel@tonic-gate  */
1065*0Sstevel@tonic-gate     _pn_clear_path(pc->path);
1066*0Sstevel@tonic-gate     if(_pn_append_to_path(pc->path, name, name_len, !literal) == NULL)
1067*0Sstevel@tonic-gate       return NULL;
1068*0Sstevel@tonic-gate /*
1069*0Sstevel@tonic-gate  * Perform a binary search for the requested filename.
1070*0Sstevel@tonic-gate  */
1071*0Sstevel@tonic-gate     match = (char **)bsearch(pc->path->name, node->files, node->nfile,
1072*0Sstevel@tonic-gate 		             sizeof(*node->files), pca_cmp_file);
1073*0Sstevel@tonic-gate     if(match) {
1074*0Sstevel@tonic-gate /*
1075*0Sstevel@tonic-gate  * Prepend the pathname in which the directory was found, which we have
1076*0Sstevel@tonic-gate  * guaranteed to end in a directory separator, to the located filename.
1077*0Sstevel@tonic-gate  */
1078*0Sstevel@tonic-gate       if(_pn_prepend_to_path(pc->path, node->dir, -1, 0) == NULL)
1079*0Sstevel@tonic-gate 	return NULL;
1080*0Sstevel@tonic-gate /*
1081*0Sstevel@tonic-gate  * Return the matching pathname unless it is rejected by the application.
1082*0Sstevel@tonic-gate  */
1083*0Sstevel@tonic-gate       if(!pc->check_fn || (*match)[0] == PCA_F_WANTED ||
1084*0Sstevel@tonic-gate 	 ((*match)[0]==PCA_F_ENIGMA && pc->check_fn(pc->data, pc->path->name))){
1085*0Sstevel@tonic-gate 	(*match)[0] = PCA_F_WANTED;
1086*0Sstevel@tonic-gate 	return pc->path->name;
1087*0Sstevel@tonic-gate       } else {
1088*0Sstevel@tonic-gate 	*(match)[0] = PCA_F_IGNORE;
1089*0Sstevel@tonic-gate       };
1090*0Sstevel@tonic-gate     };
1091*0Sstevel@tonic-gate   };
1092*0Sstevel@tonic-gate /*
1093*0Sstevel@tonic-gate  * File not found.
1094*0Sstevel@tonic-gate  */
1095*0Sstevel@tonic-gate   return NULL;
1096*0Sstevel@tonic-gate }
1097*0Sstevel@tonic-gate 
1098*0Sstevel@tonic-gate /*.......................................................................
1099*0Sstevel@tonic-gate  * A qsort() comparison function for comparing a filename string to
1100*0Sstevel@tonic-gate  * a cached filename string pointed to by a (char **) array element.
1101*0Sstevel@tonic-gate  * This ignores the initial code byte at the start of the cached filename
1102*0Sstevel@tonic-gate  * string.
1103*0Sstevel@tonic-gate  *
1104*0Sstevel@tonic-gate  * Input:
1105*0Sstevel@tonic-gate  *  v1, v2   void *  Pointers to the pointers of two strings to be compared.
1106*0Sstevel@tonic-gate  * Output:
1107*0Sstevel@tonic-gate  *  return    int    -1 -> v1 < v2.
1108*0Sstevel@tonic-gate  *                    0 -> v1 == v2
1109*0Sstevel@tonic-gate  *                    1 -> v1 > v2
1110*0Sstevel@tonic-gate  */
pca_cmp_file(const void * v1,const void * v2)1111*0Sstevel@tonic-gate static int pca_cmp_file(const void *v1, const void *v2)
1112*0Sstevel@tonic-gate {
1113*0Sstevel@tonic-gate   const char *file_name = (const char *) v1;
1114*0Sstevel@tonic-gate   const char **cache_name = (const char **) v2;
1115*0Sstevel@tonic-gate   return strcmp(file_name, *cache_name + 1);
1116*0Sstevel@tonic-gate }
1117*0Sstevel@tonic-gate 
1118*0Sstevel@tonic-gate /*.......................................................................
1119*0Sstevel@tonic-gate  * The PcaPathConf structure may have options added to it in the future.
1120*0Sstevel@tonic-gate  * To allow your application to be linked against a shared version of the
1121*0Sstevel@tonic-gate  * tecla library, without these additions causing your application to
1122*0Sstevel@tonic-gate  * crash, you should use new_PcaPathConf() to allocate such structures.
1123*0Sstevel@tonic-gate  * This will set all of the configuration options to their default values,
1124*0Sstevel@tonic-gate  * which you can then change before passing the structure to
1125*0Sstevel@tonic-gate  * pca_path_completions().
1126*0Sstevel@tonic-gate  *
1127*0Sstevel@tonic-gate  * Input:
1128*0Sstevel@tonic-gate  *  pc         PathCache *  The filename cache in which to look for
1129*0Sstevel@tonic-gate  *                          file name completions.
1130*0Sstevel@tonic-gate  * Output:
1131*0Sstevel@tonic-gate  *  return   PcaPathConf *  The new configuration structure, or NULL
1132*0Sstevel@tonic-gate  *                          on error. A descripition of the error
1133*0Sstevel@tonic-gate  *                          can be found by calling pca_last_error(pc).
1134*0Sstevel@tonic-gate  */
new_PcaPathConf(PathCache * pc)1135*0Sstevel@tonic-gate PcaPathConf *new_PcaPathConf(PathCache *pc)
1136*0Sstevel@tonic-gate {
1137*0Sstevel@tonic-gate   PcaPathConf *ppc;  /* The object to be returned */
1138*0Sstevel@tonic-gate /*
1139*0Sstevel@tonic-gate  * Check the arguments.
1140*0Sstevel@tonic-gate  */
1141*0Sstevel@tonic-gate   if(!pc)
1142*0Sstevel@tonic-gate     return NULL;
1143*0Sstevel@tonic-gate /*
1144*0Sstevel@tonic-gate  * Allocate the container.
1145*0Sstevel@tonic-gate  */
1146*0Sstevel@tonic-gate   ppc = (PcaPathConf *)malloc(sizeof(PcaPathConf));
1147*0Sstevel@tonic-gate   if(!ppc) {
1148*0Sstevel@tonic-gate     _err_record_msg(pc->err, "Insufficient memory.", END_ERR_MSG);
1149*0Sstevel@tonic-gate     return NULL;
1150*0Sstevel@tonic-gate   };
1151*0Sstevel@tonic-gate /*
1152*0Sstevel@tonic-gate  * Before attempting any operation that might fail, initialize the
1153*0Sstevel@tonic-gate  * container at least up to the point at which it can safely be passed
1154*0Sstevel@tonic-gate  * to del_PcaPathConf().
1155*0Sstevel@tonic-gate  */
1156*0Sstevel@tonic-gate   if(pca_init_PcaPathConf(ppc, pc))
1157*0Sstevel@tonic-gate     return del_PcaPathConf(ppc);
1158*0Sstevel@tonic-gate   return ppc;
1159*0Sstevel@tonic-gate }
1160*0Sstevel@tonic-gate 
1161*0Sstevel@tonic-gate /*.......................................................................
1162*0Sstevel@tonic-gate  * Initialize a PcaPathConf configuration structure with defaults.
1163*0Sstevel@tonic-gate  *
1164*0Sstevel@tonic-gate  * Input:
1165*0Sstevel@tonic-gate  *  ppc   PcaPathConf *  The structre to be initialized.
1166*0Sstevel@tonic-gate  *  pc      PathCache *  The cache in which completions will be looked up.
1167*0Sstevel@tonic-gate  * Output:
1168*0Sstevel@tonic-gate  *  return        int    0 - OK.
1169*0Sstevel@tonic-gate  *                       1 - Error. A description of the error can be
1170*0Sstevel@tonic-gate  *                           obtained by calling pca_last_error(pc).
1171*0Sstevel@tonic-gate  */
pca_init_PcaPathConf(PcaPathConf * ppc,PathCache * pc)1172*0Sstevel@tonic-gate static int pca_init_PcaPathConf(PcaPathConf *ppc, PathCache *pc)
1173*0Sstevel@tonic-gate {
1174*0Sstevel@tonic-gate /*
1175*0Sstevel@tonic-gate  * Check the arguments.
1176*0Sstevel@tonic-gate  */
1177*0Sstevel@tonic-gate   if(!pc)
1178*0Sstevel@tonic-gate     return 1;
1179*0Sstevel@tonic-gate /*
1180*0Sstevel@tonic-gate  * Set the default options.
1181*0Sstevel@tonic-gate  */
1182*0Sstevel@tonic-gate   ppc->id = PPC_ID_CODE;
1183*0Sstevel@tonic-gate   ppc->pc = pc;
1184*0Sstevel@tonic-gate   ppc->escaped = 1;
1185*0Sstevel@tonic-gate   ppc->file_start = -1;
1186*0Sstevel@tonic-gate   return 0;
1187*0Sstevel@tonic-gate }
1188*0Sstevel@tonic-gate 
1189*0Sstevel@tonic-gate /*.......................................................................
1190*0Sstevel@tonic-gate  * Delete a PcaPathConf object.
1191*0Sstevel@tonic-gate  *
1192*0Sstevel@tonic-gate  * Input:
1193*0Sstevel@tonic-gate  *  ppc    PcaPathConf *  The object to be deleted.
1194*0Sstevel@tonic-gate  * Output:
1195*0Sstevel@tonic-gate  *  return PcaPathConf *  The deleted object (always NULL).
1196*0Sstevel@tonic-gate  */
del_PcaPathConf(PcaPathConf * ppc)1197*0Sstevel@tonic-gate PcaPathConf *del_PcaPathConf(PcaPathConf *ppc)
1198*0Sstevel@tonic-gate {
1199*0Sstevel@tonic-gate   if(ppc) {
1200*0Sstevel@tonic-gate     ppc->pc = NULL;  /* It is up to the caller to delete the cache */
1201*0Sstevel@tonic-gate /*
1202*0Sstevel@tonic-gate  * Delete the container.
1203*0Sstevel@tonic-gate  */
1204*0Sstevel@tonic-gate     free(ppc);
1205*0Sstevel@tonic-gate   };
1206*0Sstevel@tonic-gate   return NULL;
1207*0Sstevel@tonic-gate }
1208*0Sstevel@tonic-gate 
1209*0Sstevel@tonic-gate /*.......................................................................
1210*0Sstevel@tonic-gate  * pca_path_completions() is a completion callback function for use
1211*0Sstevel@tonic-gate  * directly with cpl_complete_word() or gl_customize_completions(), or
1212*0Sstevel@tonic-gate  * indirectly from your own completion callback function. It requires
1213*0Sstevel@tonic-gate  * that a CpaPathArgs object be passed via its 'void *data' argument.
1214*0Sstevel@tonic-gate  */
CPL_MATCH_FN(pca_path_completions)1215*0Sstevel@tonic-gate CPL_MATCH_FN(pca_path_completions)
1216*0Sstevel@tonic-gate {
1217*0Sstevel@tonic-gate   PcaPathConf *ppc;       /* The configuration arguments */
1218*0Sstevel@tonic-gate   PathCache *pc;          /* The cache in which to look for completions */
1219*0Sstevel@tonic-gate   PathNode *node;         /* A node in the list of directories in the path */
1220*0Sstevel@tonic-gate   const char *filename;   /* The name of the file being looked at */
1221*0Sstevel@tonic-gate   const char *start_path; /* The pointer to the start of the pathname */
1222*0Sstevel@tonic-gate                           /*  in line[]. */
1223*0Sstevel@tonic-gate   int word_start;         /* The index in line[] corresponding to start_path */
1224*0Sstevel@tonic-gate   const char *prefix;     /* The file-name prefix being searched for */
1225*0Sstevel@tonic-gate   size_t prefix_len;      /* The length of the prefix being completed */
1226*0Sstevel@tonic-gate   int bot;                /* The lowest index of the array not searched yet */
1227*0Sstevel@tonic-gate   int top;                /* The highest index of the array not searched yet */
1228*0Sstevel@tonic-gate /*
1229*0Sstevel@tonic-gate  * Check the arguments.
1230*0Sstevel@tonic-gate  */
1231*0Sstevel@tonic-gate   if(!cpl)
1232*0Sstevel@tonic-gate     return 1;
1233*0Sstevel@tonic-gate   if(!line || word_end < 0 || !data) {
1234*0Sstevel@tonic-gate     cpl_record_error(cpl, "pca_path_completions: Invalid arguments.");
1235*0Sstevel@tonic-gate     return 1;
1236*0Sstevel@tonic-gate   };
1237*0Sstevel@tonic-gate /*
1238*0Sstevel@tonic-gate  * Get the configuration arguments.
1239*0Sstevel@tonic-gate  */
1240*0Sstevel@tonic-gate   ppc = (PcaPathConf *) data;
1241*0Sstevel@tonic-gate /*
1242*0Sstevel@tonic-gate  * Check that the callback data is a PcaPathConf structure returned
1243*0Sstevel@tonic-gate  * by new_PcaPathConf().
1244*0Sstevel@tonic-gate  */
1245*0Sstevel@tonic-gate   if(ppc->id != PPC_ID_CODE) {
1246*0Sstevel@tonic-gate     cpl_record_error(cpl,
1247*0Sstevel@tonic-gate 		     "Invalid callback data passed to pca_path_completions()");
1248*0Sstevel@tonic-gate     return 1;
1249*0Sstevel@tonic-gate   };
1250*0Sstevel@tonic-gate /*
1251*0Sstevel@tonic-gate  * Get the filename cache.
1252*0Sstevel@tonic-gate  */
1253*0Sstevel@tonic-gate   pc = ppc->pc;
1254*0Sstevel@tonic-gate /*
1255*0Sstevel@tonic-gate  * Get the start of the file name. If not specified by the caller,
1256*0Sstevel@tonic-gate  * identify it by searching backwards in the input line for an
1257*0Sstevel@tonic-gate  * unescaped space or the start of the line.
1258*0Sstevel@tonic-gate  */
1259*0Sstevel@tonic-gate   if(ppc->file_start < 0) {
1260*0Sstevel@tonic-gate     start_path = _pu_start_of_path(line, word_end);
1261*0Sstevel@tonic-gate     if(!start_path) {
1262*0Sstevel@tonic-gate       cpl_record_error(cpl, "Unable to find the start of the file name.");
1263*0Sstevel@tonic-gate       return 1;
1264*0Sstevel@tonic-gate     };
1265*0Sstevel@tonic-gate   } else {
1266*0Sstevel@tonic-gate     start_path = line + ppc->file_start;
1267*0Sstevel@tonic-gate   };
1268*0Sstevel@tonic-gate /*
1269*0Sstevel@tonic-gate  * Get the index of the start of the word being completed.
1270*0Sstevel@tonic-gate  */
1271*0Sstevel@tonic-gate   word_start = start_path - line;
1272*0Sstevel@tonic-gate /*
1273*0Sstevel@tonic-gate  * Work out the length of the prefix that is bein completed.
1274*0Sstevel@tonic-gate  */
1275*0Sstevel@tonic-gate   prefix_len = word_end - word_start;
1276*0Sstevel@tonic-gate /*
1277*0Sstevel@tonic-gate  * If the word starts with a ~username expression or the root directory,
1278*0Sstevel@tonic-gate  * of it contains any directory separators, then completion must be
1279*0Sstevel@tonic-gate  * delegated to cpl_file_completions().
1280*0Sstevel@tonic-gate  */
1281*0Sstevel@tonic-gate   if(cpa_cmd_contains_path(start_path, prefix_len)) {
1282*0Sstevel@tonic-gate     cfc_file_start(pc->cfc, word_start);
1283*0Sstevel@tonic-gate     return cpl_file_completions(cpl, pc->cfc, line, word_end);
1284*0Sstevel@tonic-gate   };
1285*0Sstevel@tonic-gate /*
1286*0Sstevel@tonic-gate  * Look up the specified file name in each of the directories of the path,
1287*0Sstevel@tonic-gate  * in the same order that they were listed in the path, and stop as soon
1288*0Sstevel@tonic-gate  * as an instance of the file is found.
1289*0Sstevel@tonic-gate  */
1290*0Sstevel@tonic-gate   for(node=pc->head; node; node=node->next) {
1291*0Sstevel@tonic-gate /*
1292*0Sstevel@tonic-gate  * If the directory of the latest node is a relative pathname,
1293*0Sstevel@tonic-gate  * scan it for files of interest.
1294*0Sstevel@tonic-gate  */
1295*0Sstevel@tonic-gate     if(node->relative) {
1296*0Sstevel@tonic-gate       rst_CacheMem(node->mem);
1297*0Sstevel@tonic-gate       if(pca_scan_dir(pc, node->dir, node->mem) < 1)
1298*0Sstevel@tonic-gate 	continue;
1299*0Sstevel@tonic-gate       node->files = node->mem->files;
1300*0Sstevel@tonic-gate       node->nfile = node->mem->nfiles;
1301*0Sstevel@tonic-gate     };
1302*0Sstevel@tonic-gate /*
1303*0Sstevel@tonic-gate  * If needed, make a copy of the file-name being matched, with
1304*0Sstevel@tonic-gate  * escapes removed. Note that we need to do this anew every loop
1305*0Sstevel@tonic-gate  * iteration, because the above call to pca_scan_dir() uses
1306*0Sstevel@tonic-gate  * pc->path.
1307*0Sstevel@tonic-gate  */
1308*0Sstevel@tonic-gate     prefix = pca_prepare_prefix(pc, start_path, prefix_len, ppc->escaped);
1309*0Sstevel@tonic-gate     if(!prefix)
1310*0Sstevel@tonic-gate       return 1;
1311*0Sstevel@tonic-gate /*
1312*0Sstevel@tonic-gate  * The directory entries are sorted, so we can perform a binary
1313*0Sstevel@tonic-gate  * search for an instance of the prefix being searched for.
1314*0Sstevel@tonic-gate  */
1315*0Sstevel@tonic-gate     bot = 0;
1316*0Sstevel@tonic-gate     top = node->nfile - 1;
1317*0Sstevel@tonic-gate     while(top >= bot) {
1318*0Sstevel@tonic-gate       int mid = (top + bot)/2;
1319*0Sstevel@tonic-gate       int test = strncmp(node->files[mid]+1, prefix, prefix_len);
1320*0Sstevel@tonic-gate       if(test > 0)
1321*0Sstevel@tonic-gate 	top = mid - 1;
1322*0Sstevel@tonic-gate       else if(test < 0)
1323*0Sstevel@tonic-gate 	bot = mid + 1;
1324*0Sstevel@tonic-gate       else {
1325*0Sstevel@tonic-gate 	top = bot = mid;
1326*0Sstevel@tonic-gate 	break;
1327*0Sstevel@tonic-gate       };
1328*0Sstevel@tonic-gate     };
1329*0Sstevel@tonic-gate /*
1330*0Sstevel@tonic-gate  * If we found a match, look to see if any of its neigbors also match.
1331*0Sstevel@tonic-gate  */
1332*0Sstevel@tonic-gate     if(top == bot) {
1333*0Sstevel@tonic-gate       while(--bot >= 0 && strncmp(node->files[bot]+1, prefix, prefix_len) == 0)
1334*0Sstevel@tonic-gate 	;
1335*0Sstevel@tonic-gate       while(++top < node->nfile &&
1336*0Sstevel@tonic-gate 	    strncmp(node->files[top]+1, prefix, prefix_len) == 0)
1337*0Sstevel@tonic-gate 	;
1338*0Sstevel@tonic-gate /*
1339*0Sstevel@tonic-gate  * We will have gone one too far in each direction.
1340*0Sstevel@tonic-gate  */
1341*0Sstevel@tonic-gate       bot++;
1342*0Sstevel@tonic-gate       top--;
1343*0Sstevel@tonic-gate /*
1344*0Sstevel@tonic-gate  * Add the completions to the list after checking them against the
1345*0Sstevel@tonic-gate  * callers requirements.
1346*0Sstevel@tonic-gate  */
1347*0Sstevel@tonic-gate       for( ; bot<=top; bot++) {
1348*0Sstevel@tonic-gate 	char *match = node->files[bot];
1349*0Sstevel@tonic-gate /*
1350*0Sstevel@tonic-gate  * Form the full pathname of the file.
1351*0Sstevel@tonic-gate  */
1352*0Sstevel@tonic-gate 	_pn_clear_path(pc->path);
1353*0Sstevel@tonic-gate 	if(_pn_append_to_path(pc->path, node->dir, -1, 0) == NULL ||
1354*0Sstevel@tonic-gate 	   _pn_append_to_path(pc->path, match+1, -1, 0) == NULL) {
1355*0Sstevel@tonic-gate 	  _err_record_msg(pc->err, "Insufficient memory to complete file name",
1356*0Sstevel@tonic-gate 			  END_ERR_MSG);
1357*0Sstevel@tonic-gate 	  return 1;
1358*0Sstevel@tonic-gate 	};
1359*0Sstevel@tonic-gate /*
1360*0Sstevel@tonic-gate  * Should the file be included in the list of completions?
1361*0Sstevel@tonic-gate  */
1362*0Sstevel@tonic-gate 	if(!pc->check_fn || match[0] == PCA_F_WANTED ||
1363*0Sstevel@tonic-gate 	   (match[0]==PCA_F_ENIGMA && pc->check_fn(pc->data, pc->path->name))) {
1364*0Sstevel@tonic-gate 	  match[0] = PCA_F_WANTED;
1365*0Sstevel@tonic-gate /*
1366*0Sstevel@tonic-gate  * Copy the completion suffix into the work pathname pc->path->name,
1367*0Sstevel@tonic-gate  * adding backslash escapes if needed.
1368*0Sstevel@tonic-gate  */
1369*0Sstevel@tonic-gate 	  if(pca_prepare_suffix(pc, match + 1 + prefix_len,
1370*0Sstevel@tonic-gate 				ppc->escaped))
1371*0Sstevel@tonic-gate 	    return 1;
1372*0Sstevel@tonic-gate /*
1373*0Sstevel@tonic-gate  * Record the completion.
1374*0Sstevel@tonic-gate  */
1375*0Sstevel@tonic-gate 	  if(cpl_add_completion(cpl, line, word_start, word_end, pc->path->name,
1376*0Sstevel@tonic-gate 				"", " "))
1377*0Sstevel@tonic-gate 	    return 1;
1378*0Sstevel@tonic-gate /*
1379*0Sstevel@tonic-gate  * The file was rejected by the application.
1380*0Sstevel@tonic-gate  */
1381*0Sstevel@tonic-gate 	} else {
1382*0Sstevel@tonic-gate 	  match[0] = PCA_F_IGNORE;
1383*0Sstevel@tonic-gate 	};
1384*0Sstevel@tonic-gate       };
1385*0Sstevel@tonic-gate     };
1386*0Sstevel@tonic-gate   };
1387*0Sstevel@tonic-gate /*
1388*0Sstevel@tonic-gate  * We now need to search for subdirectories of the current directory which
1389*0Sstevel@tonic-gate  * have matching prefixes. First, if needed, make a copy of the word being
1390*0Sstevel@tonic-gate  * matched, with escapes removed.
1391*0Sstevel@tonic-gate  */
1392*0Sstevel@tonic-gate   prefix = pca_prepare_prefix(pc, start_path, prefix_len, ppc->escaped);
1393*0Sstevel@tonic-gate   if(!prefix)
1394*0Sstevel@tonic-gate     return 1;
1395*0Sstevel@tonic-gate /*
1396*0Sstevel@tonic-gate  * Now open the current directory.
1397*0Sstevel@tonic-gate  */
1398*0Sstevel@tonic-gate   if(_dr_open_dir(pc->dr, FS_PWD, NULL))
1399*0Sstevel@tonic-gate     return 0;
1400*0Sstevel@tonic-gate /*
1401*0Sstevel@tonic-gate  * Scan the current directory for sub-directories whos names start with
1402*0Sstevel@tonic-gate  * the prefix that we are completing.
1403*0Sstevel@tonic-gate  */
1404*0Sstevel@tonic-gate   while((filename = _dr_next_file(pc->dr))) {
1405*0Sstevel@tonic-gate /*
1406*0Sstevel@tonic-gate  * Does the latest filename match the prefix, and is it a directory?
1407*0Sstevel@tonic-gate  */
1408*0Sstevel@tonic-gate     if(strncmp(filename, prefix, prefix_len) == 0 && _pu_path_is_dir(filename)){
1409*0Sstevel@tonic-gate /*
1410*0Sstevel@tonic-gate  * Record the completion.
1411*0Sstevel@tonic-gate  */
1412*0Sstevel@tonic-gate       if(pca_prepare_suffix(pc, filename + prefix_len, ppc->escaped) ||
1413*0Sstevel@tonic-gate 	 cpl_add_completion(cpl, line, word_start, word_end, pc->path->name,
1414*0Sstevel@tonic-gate 			    FS_DIR_SEP, FS_DIR_SEP))
1415*0Sstevel@tonic-gate 	return 1;
1416*0Sstevel@tonic-gate /*
1417*0Sstevel@tonic-gate  * The prefix in pc->path->name will have been overwritten by
1418*0Sstevel@tonic-gate  * pca_prepare_suffix(). Restore it here.
1419*0Sstevel@tonic-gate  */
1420*0Sstevel@tonic-gate       prefix = pca_prepare_prefix(pc, start_path, prefix_len, ppc->escaped);
1421*0Sstevel@tonic-gate       if(!prefix)
1422*0Sstevel@tonic-gate 	return 1;
1423*0Sstevel@tonic-gate     };
1424*0Sstevel@tonic-gate   };
1425*0Sstevel@tonic-gate   _dr_close_dir(pc->dr);
1426*0Sstevel@tonic-gate   return 0;
1427*0Sstevel@tonic-gate }
1428*0Sstevel@tonic-gate 
1429*0Sstevel@tonic-gate /*.......................................................................
1430*0Sstevel@tonic-gate  * Using the work buffer pc->path, make a suitably escaped copy of a
1431*0Sstevel@tonic-gate  * given completion suffix, ready to be passed to cpl_add_completion().
1432*0Sstevel@tonic-gate  *
1433*0Sstevel@tonic-gate  * Input:
1434*0Sstevel@tonic-gate  *  pc      PathCache *  The filename cache resource object.
1435*0Sstevel@tonic-gate  *  suffix       char *  The suffix to be copied.
1436*0Sstevel@tonic-gate  *  add_escapes   int    If true, escape special characters.
1437*0Sstevel@tonic-gate  * Output:
1438*0Sstevel@tonic-gate  *  return        int    0 - OK.
1439*0Sstevel@tonic-gate  *                       1 - Error.
1440*0Sstevel@tonic-gate  */
pca_prepare_suffix(PathCache * pc,const char * suffix,int add_escapes)1441*0Sstevel@tonic-gate static int pca_prepare_suffix(PathCache *pc, const char *suffix,
1442*0Sstevel@tonic-gate 			      int add_escapes)
1443*0Sstevel@tonic-gate {
1444*0Sstevel@tonic-gate   const char *sptr; /* A pointer into suffix[] */
1445*0Sstevel@tonic-gate   int nbsl;         /* The number of backslashes to add to the suffix */
1446*0Sstevel@tonic-gate   int i;
1447*0Sstevel@tonic-gate /*
1448*0Sstevel@tonic-gate  * How long is the suffix?
1449*0Sstevel@tonic-gate  */
1450*0Sstevel@tonic-gate   int suffix_len = strlen(suffix);
1451*0Sstevel@tonic-gate /*
1452*0Sstevel@tonic-gate  * Clear the work buffer.
1453*0Sstevel@tonic-gate  */
1454*0Sstevel@tonic-gate   _pn_clear_path(pc->path);
1455*0Sstevel@tonic-gate /*
1456*0Sstevel@tonic-gate  * Count the number of backslashes that will have to be added to
1457*0Sstevel@tonic-gate  * escape spaces, tabs, backslashes and wildcard characters.
1458*0Sstevel@tonic-gate  */
1459*0Sstevel@tonic-gate   nbsl = 0;
1460*0Sstevel@tonic-gate   if(add_escapes) {
1461*0Sstevel@tonic-gate     for(sptr = suffix; *sptr; sptr++) {
1462*0Sstevel@tonic-gate       switch(*sptr) {
1463*0Sstevel@tonic-gate       case ' ': case '\t': case '\\': case '*': case '?': case '[':
1464*0Sstevel@tonic-gate 	nbsl++;
1465*0Sstevel@tonic-gate 	break;
1466*0Sstevel@tonic-gate       };
1467*0Sstevel@tonic-gate     };
1468*0Sstevel@tonic-gate   };
1469*0Sstevel@tonic-gate /*
1470*0Sstevel@tonic-gate  * Arrange for the output path buffer to have sufficient room for the
1471*0Sstevel@tonic-gate  * both the suffix and any backslashes that have to be inserted.
1472*0Sstevel@tonic-gate  */
1473*0Sstevel@tonic-gate   if(_pn_resize_path(pc->path, suffix_len + nbsl) == NULL) {
1474*0Sstevel@tonic-gate     _err_record_msg(pc->err, "Insufficient memory to complete file name",
1475*0Sstevel@tonic-gate 		    END_ERR_MSG);
1476*0Sstevel@tonic-gate     return 1;
1477*0Sstevel@tonic-gate   };
1478*0Sstevel@tonic-gate /*
1479*0Sstevel@tonic-gate  * If the suffix doesn't need any escapes, copy it directly into the
1480*0Sstevel@tonic-gate  * work buffer.
1481*0Sstevel@tonic-gate  */
1482*0Sstevel@tonic-gate   if(nbsl==0) {
1483*0Sstevel@tonic-gate     strlcpy(pc->path->name, suffix, pc->path->dim);
1484*0Sstevel@tonic-gate   } else {
1485*0Sstevel@tonic-gate /*
1486*0Sstevel@tonic-gate  * Make a copy with special characters escaped?
1487*0Sstevel@tonic-gate  */
1488*0Sstevel@tonic-gate     if(nbsl > 0) {
1489*0Sstevel@tonic-gate       const char *src = suffix;
1490*0Sstevel@tonic-gate       char *dst = pc->path->name;
1491*0Sstevel@tonic-gate       for(i=0; i<suffix_len; i++) {
1492*0Sstevel@tonic-gate 	switch(*src) {
1493*0Sstevel@tonic-gate 	case ' ': case '\t': case '\\': case '*': case '?': case '[':
1494*0Sstevel@tonic-gate 	  *dst++ = '\\';
1495*0Sstevel@tonic-gate 	};
1496*0Sstevel@tonic-gate 	*dst++ = *src++;
1497*0Sstevel@tonic-gate       };
1498*0Sstevel@tonic-gate       *dst = '\0';
1499*0Sstevel@tonic-gate     };
1500*0Sstevel@tonic-gate   };
1501*0Sstevel@tonic-gate   return 0;
1502*0Sstevel@tonic-gate }
1503*0Sstevel@tonic-gate 
1504*0Sstevel@tonic-gate /*.......................................................................
1505*0Sstevel@tonic-gate  * Return non-zero if the specified string appears to start with a pathname.
1506*0Sstevel@tonic-gate  *
1507*0Sstevel@tonic-gate  * Input:
1508*0Sstevel@tonic-gate  *  prefix  const char *  The filename prefix to check.
1509*0Sstevel@tonic-gate  *  prefix_len     int    The length of the prefix.
1510*0Sstevel@tonic-gate  * Output:
1511*0Sstevel@tonic-gate  *  return         int    0 - Doesn't start with a path name.
1512*0Sstevel@tonic-gate  *                        1 - Does start with a path name.
1513*0Sstevel@tonic-gate  */
cpa_cmd_contains_path(const char * prefix,int prefix_len)1514*0Sstevel@tonic-gate static int cpa_cmd_contains_path(const char *prefix, int prefix_len)
1515*0Sstevel@tonic-gate {
1516*0Sstevel@tonic-gate   int i;
1517*0Sstevel@tonic-gate /*
1518*0Sstevel@tonic-gate  * If the filename starts with a ~, then this implies a ~username
1519*0Sstevel@tonic-gate  * expression, which constitutes a pathname.
1520*0Sstevel@tonic-gate  */
1521*0Sstevel@tonic-gate   if(*prefix == '~')
1522*0Sstevel@tonic-gate     return 1;
1523*0Sstevel@tonic-gate /*
1524*0Sstevel@tonic-gate  * If the filename starts with the root directory, then it obviously
1525*0Sstevel@tonic-gate  * starts with a pathname.
1526*0Sstevel@tonic-gate  */
1527*0Sstevel@tonic-gate   if(prefix_len >= FS_ROOT_DIR_LEN &&
1528*0Sstevel@tonic-gate      strncmp(prefix, FS_ROOT_DIR, FS_ROOT_DIR_LEN) == 0)
1529*0Sstevel@tonic-gate     return 1;
1530*0Sstevel@tonic-gate /*
1531*0Sstevel@tonic-gate  * Search the prefix for directory separators, returning as soon as
1532*0Sstevel@tonic-gate  * any are found, since their presence indicates that the filename
1533*0Sstevel@tonic-gate  * starts with a pathname specification (valid or otherwise).
1534*0Sstevel@tonic-gate  */
1535*0Sstevel@tonic-gate   for(i=0; i<prefix_len; i++) {
1536*0Sstevel@tonic-gate     if(prefix_len - i >= FS_DIR_SEP_LEN &&
1537*0Sstevel@tonic-gate        strncmp(prefix + i, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0)
1538*0Sstevel@tonic-gate       return 1;
1539*0Sstevel@tonic-gate   };
1540*0Sstevel@tonic-gate /*
1541*0Sstevel@tonic-gate  * The file name doesn't appear to start with a pathname specification.
1542*0Sstevel@tonic-gate  */
1543*0Sstevel@tonic-gate   return 0;
1544*0Sstevel@tonic-gate }
1545*0Sstevel@tonic-gate 
1546*0Sstevel@tonic-gate /*.......................................................................
1547*0Sstevel@tonic-gate  * If needed make a new copy of the prefix being matched, in pc->path->name,
1548*0Sstevel@tonic-gate  * but with escapes removed. If no escapes are to be removed, simply return
1549*0Sstevel@tonic-gate  * the original prefix string.
1550*0Sstevel@tonic-gate  *
1551*0Sstevel@tonic-gate  * Input:
1552*0Sstevel@tonic-gate  *  pc      PathCache *   The cache being searched.
1553*0Sstevel@tonic-gate  *  prefix const char *   The prefix to be processed.
1554*0Sstevel@tonic-gate  *  prefix_len size_t     The length of the prefix.
1555*0Sstevel@tonic-gate  *  escaped       int     If true, return a copy with escapes removed.
1556*0Sstevel@tonic-gate  * Output:
1557*0Sstevel@tonic-gate  *  return const char *   The prepared prefix, or NULL on error, in
1558*0Sstevel@tonic-gate  *                        which case an error message will have been
1559*0Sstevel@tonic-gate  *                        left in pc->err.
1560*0Sstevel@tonic-gate  */
pca_prepare_prefix(PathCache * pc,const char * prefix,size_t prefix_len,int escaped)1561*0Sstevel@tonic-gate static const char *pca_prepare_prefix(PathCache *pc, const char *prefix,
1562*0Sstevel@tonic-gate 				      size_t prefix_len, int escaped)
1563*0Sstevel@tonic-gate {
1564*0Sstevel@tonic-gate /*
1565*0Sstevel@tonic-gate  * Make a copy with escapes removed?
1566*0Sstevel@tonic-gate  */
1567*0Sstevel@tonic-gate   if(escaped) {
1568*0Sstevel@tonic-gate     _pn_clear_path(pc->path);
1569*0Sstevel@tonic-gate     if(_pn_append_to_path(pc->path, prefix, prefix_len, 1) == NULL) {
1570*0Sstevel@tonic-gate       _err_record_msg(pc->err, "Insufficient memory to complete filename",
1571*0Sstevel@tonic-gate 		      END_ERR_MSG);
1572*0Sstevel@tonic-gate       return NULL;
1573*0Sstevel@tonic-gate     };
1574*0Sstevel@tonic-gate     return pc->path->name;
1575*0Sstevel@tonic-gate   };
1576*0Sstevel@tonic-gate   return prefix;
1577*0Sstevel@tonic-gate }
1578*0Sstevel@tonic-gate 
1579*0Sstevel@tonic-gate /*.......................................................................
1580*0Sstevel@tonic-gate  * If backslashes in the filename should be treated as literal
1581*0Sstevel@tonic-gate  * characters, call the following function with literal=1. Otherwise
1582*0Sstevel@tonic-gate  * the default is to treat them as escape characters, used for escaping
1583*0Sstevel@tonic-gate  * spaces etc..
1584*0Sstevel@tonic-gate  *
1585*0Sstevel@tonic-gate  * Input:
1586*0Sstevel@tonic-gate  *  ppc    PcaPathConf *  The pca_path_completions() configuration object
1587*0Sstevel@tonic-gate  *                        to be configured.
1588*0Sstevel@tonic-gate  *  literal        int    Pass non-zero here to enable literal interpretation
1589*0Sstevel@tonic-gate  *                        of backslashes. Pass 0 to turn off literal
1590*0Sstevel@tonic-gate  *                        interpretation.
1591*0Sstevel@tonic-gate  */
ppc_literal_escapes(PcaPathConf * ppc,int literal)1592*0Sstevel@tonic-gate void ppc_literal_escapes(PcaPathConf *ppc, int literal)
1593*0Sstevel@tonic-gate {
1594*0Sstevel@tonic-gate   if(ppc)
1595*0Sstevel@tonic-gate     ppc->escaped = !literal;
1596*0Sstevel@tonic-gate }
1597*0Sstevel@tonic-gate 
1598*0Sstevel@tonic-gate /*.......................................................................
1599*0Sstevel@tonic-gate  * Call this function if you know where the index at which the
1600*0Sstevel@tonic-gate  * filename prefix starts in the input line. Otherwise by default,
1601*0Sstevel@tonic-gate  * or if you specify start_index to be -1, the filename is taken
1602*0Sstevel@tonic-gate  * to start after the first unescaped space preceding the cursor,
1603*0Sstevel@tonic-gate  * or the start of the line, which ever comes first.
1604*0Sstevel@tonic-gate  *
1605*0Sstevel@tonic-gate  * Input:
1606*0Sstevel@tonic-gate  *  ppc    PcaPathConf *  The pca_path_completions() configuration object
1607*0Sstevel@tonic-gate  *                        to be configured.
1608*0Sstevel@tonic-gate  *  start_index    int    The index of the start of the filename in
1609*0Sstevel@tonic-gate  *                        the input line, or -1 to select the default.
1610*0Sstevel@tonic-gate  */
ppc_file_start(PcaPathConf * ppc,int start_index)1611*0Sstevel@tonic-gate void ppc_file_start(PcaPathConf *ppc, int start_index)
1612*0Sstevel@tonic-gate {
1613*0Sstevel@tonic-gate   if(ppc)
1614*0Sstevel@tonic-gate     ppc->file_start = start_index;
1615*0Sstevel@tonic-gate }
1616*0Sstevel@tonic-gate 
1617*0Sstevel@tonic-gate /*.......................................................................
1618*0Sstevel@tonic-gate  * Expand any ~user expression found at the start of a path, leaving
1619*0Sstevel@tonic-gate  * either an empty string in pc->path if there is no ~user expression,
1620*0Sstevel@tonic-gate  * or the corresponding home directory.
1621*0Sstevel@tonic-gate  *
1622*0Sstevel@tonic-gate  * Input:
1623*0Sstevel@tonic-gate  *  pc     PathCache *  The filename cache.
1624*0Sstevel@tonic-gate  *  path  const char *  The path to expand.
1625*0Sstevel@tonic-gate  *  pathlen      int    The max number of characters to look at in path[].
1626*0Sstevel@tonic-gate  *  literal      int    If true, treat backslashes as literal characters
1627*0Sstevel@tonic-gate  *                      instead of escapes.
1628*0Sstevel@tonic-gate  * Input/Output:
1629*0Sstevel@tonic-gate  *  endp  const char *  A pointer to the next unprocessed character in
1630*0Sstevel@tonic-gate  *                      path[] will be assigned to *endp.
1631*0Sstevel@tonic-gate  * Output:
1632*0Sstevel@tonic-gate  *  return       int    0 - OK
1633*0Sstevel@tonic-gate  *                      1 - Error (a description will have been placed
1634*0Sstevel@tonic-gate  *                                 in pc->err).
1635*0Sstevel@tonic-gate  */
pca_expand_tilde(PathCache * pc,const char * path,int pathlen,int literal,const char ** endp)1636*0Sstevel@tonic-gate static int pca_expand_tilde(PathCache *pc, const char *path, int pathlen,
1637*0Sstevel@tonic-gate 			    int literal, const char **endp)
1638*0Sstevel@tonic-gate {
1639*0Sstevel@tonic-gate   const char *pptr = path;  /* A pointer into path[] */
1640*0Sstevel@tonic-gate   const char *homedir=NULL; /* A home directory */
1641*0Sstevel@tonic-gate /*
1642*0Sstevel@tonic-gate  * Clear the pathname buffer.
1643*0Sstevel@tonic-gate  */
1644*0Sstevel@tonic-gate   _pn_clear_path(pc->path);
1645*0Sstevel@tonic-gate /*
1646*0Sstevel@tonic-gate  * If the first character is a tilde, then perform home-directory
1647*0Sstevel@tonic-gate  * interpolation.
1648*0Sstevel@tonic-gate  */
1649*0Sstevel@tonic-gate   if(*pptr == '~') {
1650*0Sstevel@tonic-gate /*
1651*0Sstevel@tonic-gate  * Skip the tilde character and attempt to read the username that follows
1652*0Sstevel@tonic-gate  * it, into pc->usrnam[].
1653*0Sstevel@tonic-gate  */
1654*0Sstevel@tonic-gate     if(pca_read_username(pc, ++pptr, pathlen-1, literal, &pptr))
1655*0Sstevel@tonic-gate       return 1;
1656*0Sstevel@tonic-gate /*
1657*0Sstevel@tonic-gate  * Attempt to lookup the home directory of the user.
1658*0Sstevel@tonic-gate  */
1659*0Sstevel@tonic-gate     homedir = _hd_lookup_home_dir(pc->home, pc->usrnam);
1660*0Sstevel@tonic-gate     if(!homedir) {
1661*0Sstevel@tonic-gate       _err_record_msg(pc->err, _hd_last_home_dir_error(pc->home), END_ERR_MSG);
1662*0Sstevel@tonic-gate       return 1;
1663*0Sstevel@tonic-gate     };
1664*0Sstevel@tonic-gate /*
1665*0Sstevel@tonic-gate  * Append the home directory to the pathname string.
1666*0Sstevel@tonic-gate  */
1667*0Sstevel@tonic-gate     if(_pn_append_to_path(pc->path, homedir, -1, 0) == NULL) {
1668*0Sstevel@tonic-gate       _err_record_msg(pc->err,
1669*0Sstevel@tonic-gate 		      "Insufficient memory for home directory expansion",
1670*0Sstevel@tonic-gate 		      END_ERR_MSG);
1671*0Sstevel@tonic-gate       return 1;
1672*0Sstevel@tonic-gate     };
1673*0Sstevel@tonic-gate   };
1674*0Sstevel@tonic-gate /*
1675*0Sstevel@tonic-gate  * ~user and ~ are usually followed by a directory separator to
1676*0Sstevel@tonic-gate  * separate them from the file contained in the home directory.
1677*0Sstevel@tonic-gate  * If the home directory is the root directory, then we don't want
1678*0Sstevel@tonic-gate  * to follow the home directory by a directory separator, so we should
1679*0Sstevel@tonic-gate  * skip over it so that it doesn't get copied into the output pathname
1680*0Sstevel@tonic-gate  */
1681*0Sstevel@tonic-gate   if(homedir && strcmp(homedir, FS_ROOT_DIR) == 0 &&
1682*0Sstevel@tonic-gate      (pptr-path) + FS_DIR_SEP_LEN < pathlen &&
1683*0Sstevel@tonic-gate      strncmp(pptr, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0) {
1684*0Sstevel@tonic-gate     pptr += FS_DIR_SEP_LEN;
1685*0Sstevel@tonic-gate   };
1686*0Sstevel@tonic-gate /*
1687*0Sstevel@tonic-gate  * Return a pointer to the next unprocessed character.
1688*0Sstevel@tonic-gate  */
1689*0Sstevel@tonic-gate   *endp = pptr;
1690*0Sstevel@tonic-gate   return 0;
1691*0Sstevel@tonic-gate }
1692*0Sstevel@tonic-gate 
1693*0Sstevel@tonic-gate /*.......................................................................
1694*0Sstevel@tonic-gate  * Clear the filename status codes that are recorded before each filename
1695*0Sstevel@tonic-gate  * in the cache.
1696*0Sstevel@tonic-gate  *
1697*0Sstevel@tonic-gate  * Input:
1698*0Sstevel@tonic-gate  *  pc     PathCache *  The filename cache.
1699*0Sstevel@tonic-gate  */
pca_remove_marks(PathCache * pc)1700*0Sstevel@tonic-gate static void pca_remove_marks(PathCache *pc)
1701*0Sstevel@tonic-gate {
1702*0Sstevel@tonic-gate   PathNode *node;         /* A node in the list of directories in the path */
1703*0Sstevel@tonic-gate   int i;
1704*0Sstevel@tonic-gate /*
1705*0Sstevel@tonic-gate  * Traverse the absolute directories of the path, clearing the
1706*0Sstevel@tonic-gate  * filename status marks that precede each filename.
1707*0Sstevel@tonic-gate  */
1708*0Sstevel@tonic-gate   for(node=pc->head; node; node=node->next) {
1709*0Sstevel@tonic-gate     if(!node->relative) {
1710*0Sstevel@tonic-gate       for(i=0; i<node->nfile; i++)
1711*0Sstevel@tonic-gate 	*node->files[i] = PCA_F_ENIGMA;
1712*0Sstevel@tonic-gate     };
1713*0Sstevel@tonic-gate   };
1714*0Sstevel@tonic-gate   return;
1715*0Sstevel@tonic-gate }
1716*0Sstevel@tonic-gate 
1717*0Sstevel@tonic-gate #endif  /* ifndef WITHOUT_FILE_SYSTEM */
1718