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