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