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