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