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