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