2 * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd.
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.
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.
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.
32 #pragma ident "%Z%%M% %I% %E% SMI"
35 * If file-system access is to be excluded, this module has no function,
36 * so all of its code should be excluded.
38 #ifndef WITHOUT_FILE_SYSTEM
56 * Specify the number of elements to extend the files[] array by
57 * when it proves to be too small. This also sets the initial size
60 #define MATCH_BLK_FACT 256
63 * A list of directory iterators is maintained using nodes of the
66 typedef struct DirNode DirNode
;
68 DirNode
*next
; /* The next directory in the list */
69 DirNode
*prev
; /* The node that precedes this node in the list */
70 DirReader
*dr
; /* The directory reader object */
74 FreeList
*mem
; /* Memory for DirNode list nodes */
75 DirNode
*head
; /* The head of the list of used and unused cache nodes */
76 DirNode
*next
; /* The next unused node between head and tail */
77 DirNode
*tail
; /* The tail of the list of unused cache nodes */
81 * Specify how many directory cache nodes to allocate at a time.
83 #define DIR_CACHE_BLK 20
86 * Set the maximum length allowed for usernames.
91 * Set the maximum length allowed for environment variable names.
96 * Set the default number of spaces place between columns when listing
97 * a set of expansions.
102 ErrMsg
*err
; /* The error reporting buffer */
103 StringGroup
*sg
; /* A list of string segments in which */
104 /* matching filenames are stored. */
105 DirCache cache
; /* The cache of directory reader objects */
106 PathName
*path
; /* The pathname being matched */
107 HomeDir
*home
; /* Home-directory lookup object */
108 int files_dim
; /* The allocated dimension of result.files[] */
109 char usrnam
[USR_LEN
+1]; /* A user name */
110 char envnam
[ENV_LEN
+1]; /* An environment variable name */
111 FileExpansion result
; /* The container used to return the results of */
112 /* expanding a path. */
115 static int ef_record_pathname(ExpandFile
*ef
, const char *pathname
,
117 static char *ef_cache_pathname(ExpandFile
*ef
, const char *pathname
,
119 static void ef_clear_files(ExpandFile
*ef
);
121 static DirNode
*ef_open_dir(ExpandFile
*ef
, const char *pathname
);
122 static DirNode
*ef_close_dir(ExpandFile
*ef
, DirNode
*node
);
123 static char *ef_expand_special(ExpandFile
*ef
, const char *path
, int pathlen
);
124 static int ef_match_relative_pathname(ExpandFile
*ef
, DirReader
*dr
,
125 const char *pattern
, int separate
);
126 static int ef_matches_range(int c
, const char *pattern
, const char **endp
);
127 static int ef_string_matches_pattern(const char *file
, const char *pattern
,
128 int xplicit
, const char *nextp
);
129 static int ef_cmp_strings(const void *v1
, const void *v2
);
132 * Encapsulate the formatting information needed to layout a
133 * multi-column listing of expansions.
136 int term_width
; /* The width of the terminal (characters) */
137 int column_width
; /* The number of characters within in each column. */
138 int ncol
; /* The number of columns needed */
139 int nline
; /* The number of lines needed */
143 * Given the current terminal width, and a list of file expansions,
144 * determine how to best use the terminal width to display a multi-column
145 * listing of expansions.
147 static void ef_plan_listing(FileExpansion
*result
, int term_width
,
151 * Display a given line of a multi-column list of file-expansions.
153 static int ef_format_line(FileExpansion
*result
, EfListFormat
*fmt
, int lnum
,
154 GlWriteFn
*write_fn
, void *data
);
156 /*.......................................................................
157 * Create the resources needed to expand filenames.
160 * return ExpandFile * The new object, or NULL on error.
162 ExpandFile
*new_ExpandFile(void)
164 ExpandFile
*ef
; /* The object to be returned */
166 * Allocate the container.
168 ef
= (ExpandFile
*) malloc(sizeof(ExpandFile
));
174 * Before attempting any operation that might fail, initialize the
175 * container at least up to the point at which it can safely be passed
176 * to del_ExpandFile().
180 ef
->cache
.mem
= NULL
;
181 ef
->cache
.head
= NULL
;
182 ef
->cache
.next
= NULL
;
183 ef
->cache
.tail
= NULL
;
186 ef
->result
.files
= NULL
;
187 ef
->result
.nfile
= 0;
188 ef
->usrnam
[0] = '\0';
189 ef
->envnam
[0] = '\0';
191 * Allocate a place to record error messages.
193 ef
->err
= _new_ErrMsg();
195 return del_ExpandFile(ef
);
197 * Allocate a list of string segments for storing filenames.
199 ef
->sg
= _new_StringGroup(_pu_pathname_dim());
201 return del_ExpandFile(ef
);
203 * Allocate a freelist for allocating directory cache nodes.
205 ef
->cache
.mem
= _new_FreeList(sizeof(DirNode
), DIR_CACHE_BLK
);
207 return del_ExpandFile(ef
);
209 * Allocate a pathname buffer.
211 ef
->path
= _new_PathName();
213 return del_ExpandFile(ef
);
215 * Allocate an object for looking up home-directories.
217 ef
->home
= _new_HomeDir();
219 return del_ExpandFile(ef
);
221 * Allocate an array for files. This will be extended later if needed.
223 ef
->files_dim
= MATCH_BLK_FACT
;
224 ef
->result
.files
= (char **) malloc(sizeof(ef
->result
.files
[0]) *
226 if(!ef
->result
.files
) {
228 return del_ExpandFile(ef
);
233 /*.......................................................................
234 * Delete a ExpandFile object.
237 * ef ExpandFile * The object to be deleted.
239 * return ExpandFile * The deleted object (always NULL).
241 ExpandFile
*del_ExpandFile(ExpandFile
*ef
)
246 * Delete the string segments.
248 ef
->sg
= _del_StringGroup(ef
->sg
);
250 * Delete the cached directory readers.
252 for(dnode
=ef
->cache
.head
; dnode
; dnode
=dnode
->next
)
253 dnode
->dr
= _del_DirReader(dnode
->dr
);
255 * Delete the memory from which the DirNode list was allocated, thus
256 * deleting the list at the same time.
258 ef
->cache
.mem
= _del_FreeList(ef
->cache
.mem
, 1);
259 ef
->cache
.head
= ef
->cache
.tail
= ef
->cache
.next
= NULL
;
261 * Delete the pathname buffer.
263 ef
->path
= _del_PathName(ef
->path
);
265 * Delete the home-directory lookup object.
267 ef
->home
= _del_HomeDir(ef
->home
);
269 * Delete the array of pointers to files.
271 if(ef
->result
.files
) {
272 free(ef
->result
.files
);
273 ef
->result
.files
= NULL
;
276 * Delete the error report buffer.
278 ef
->err
= _del_ErrMsg(ef
->err
);
280 * Delete the container.
287 /*.......................................................................
288 * Expand a pathname, converting ~user/ and ~/ patterns at the start
289 * of the pathname to the corresponding home directories, replacing
290 * $envvar with the value of the corresponding environment variable,
291 * and then, if there are any wildcards, matching these against existing
294 * If no errors occur, a container is returned containing the array of
295 * files that resulted from the expansion. If there were no wildcards
296 * in the input pathname, this will contain just the original pathname
297 * after expansion of ~ and $ expressions. If there were any wildcards,
298 * then the array will contain the files that matched them. Note that
299 * if there were any wildcards but no existing files match them, this
300 * is counted as an error and NULL is returned.
302 * The supported wildcards and their meanings are:
303 * * - Match any sequence of zero or more characters.
304 * ? - Match any single character.
305 * [chars] - Match any single character that appears in 'chars'.
306 * If 'chars' contains an expression of the form a-b,
307 * then any character between a and b, including a and b,
308 * matches. The '-' character looses its special meaning
309 * as a range specifier when it appears at the start
310 * of the sequence of characters.
311 * [^chars] - The same as [chars] except that it matches any single
312 * character that doesn't appear in 'chars'.
314 * Wildcard expressions are applied to individual filename components.
315 * They don't match across directory separators. A '.' character at
316 * the beginning of a filename component must also be matched
317 * explicitly by a '.' character in the input pathname, since these
318 * are UNIX's hidden files.
321 * ef ExpandFile * The pathname expansion resource object.
322 * path char * The path name to be expanded.
323 * pathlen int The length of the suffix of path[] that
324 * constitutes the filename to be expanded,
325 * or -1 to specify that the whole of the
326 * path string should be used. Note that
327 * regardless of the value of this argument,
328 * path[] must contain a '\0' terminated
329 * string, since this function checks that
330 * pathlen isn't mistakenly too long.
332 * return FileExpansion * A pointer to a container within the given
333 * ExpandFile object. This contains an array
334 * of the pathnames that resulted from expanding
335 * ~ and $ expressions and from matching any
336 * wildcards, sorted into lexical order.
337 * This container and its contents will be
338 * recycled on subsequent calls, so if you need
339 * to keep the results of two successive runs,
340 * you will either have to allocate a private
341 * copy of the array, or use two ExpandFile
344 * On error NULL is returned. A description
345 * of the error can be acquired by calling the
346 * ef_last_error() function.
348 FileExpansion
*ef_expand_file(ExpandFile
*ef
, const char *path
, int pathlen
)
350 DirNode
*dnode
; /* A directory-reader cache node */
351 const char *dirname
; /* The name of the top level directory of the search */
352 const char *pptr
; /* A pointer into path[] */
353 int wild
; /* True if the path contains any wildcards */
355 * Check the arguments.
359 _err_record_msg(ef
->err
, "ef_expand_file: NULL path argument",
366 * If the caller specified that the whole of path[] be matched,
367 * work out the corresponding length.
369 if(pathlen
< 0 || pathlen
> strlen(path
))
370 pathlen
= strlen(path
);
372 * Discard previous expansion results.
376 * Preprocess the path, expanding ~/, ~user/ and $envvar references,
377 * using ef->path as a work directory and returning a pointer to
378 * a copy of the resulting pattern in the cache.
380 path
= ef_expand_special(ef
, path
, pathlen
);
384 * Clear the pathname buffer.
386 _pn_clear_path(ef
->path
);
388 * Does the pathname contain any wildcards?
390 for(wild
=0,pptr
=path
; !wild
&& *pptr
; pptr
++) {
392 case '\\': /* Skip escaped characters */
396 case '*': case '?': case '[': /* A wildcard character? */
402 * If there are no wildcards to match, copy the current expanded
403 * path into the output array, removing backslash escapes while doing so.
406 if(ef_record_pathname(ef
, path
, 1))
409 * Does the filename exist?
411 ef
->result
.exists
= _pu_file_exists(ef
->result
.files
[0]);
413 * Match wildcards against existing files.
417 * Only existing files that match the pattern will be returned in the
420 ef
->result
.exists
= 1;
422 * Treat matching of the root-directory as a special case since it
423 * isn't contained in a directory.
425 if(strcmp(path
, FS_ROOT_DIR
) == 0) {
426 if(ef_record_pathname(ef
, FS_ROOT_DIR
, 0))
430 * What should the top level directory of the search be?
432 if(strncmp(path
, FS_ROOT_DIR
, FS_ROOT_DIR_LEN
) == 0) {
433 dirname
= FS_ROOT_DIR
;
434 if(!_pn_append_to_path(ef
->path
, FS_ROOT_DIR
, -1, 0)) {
435 _err_record_msg(ef
->err
, "Insufficient memory to record path",
439 path
+= FS_ROOT_DIR_LEN
;
444 * Open the top-level directory of the search.
446 dnode
= ef_open_dir(ef
, dirname
);
450 * Recursively match successive directory components of the path.
452 if(ef_match_relative_pathname(ef
, dnode
->dr
, path
, 0)) {
453 dnode
= ef_close_dir(ef
, dnode
);
459 dnode
= ef_close_dir(ef
, dnode
);
464 if(ef
->result
.nfile
< 1) {
465 _err_record_msg(ef
->err
, "No files match", END_ERR_MSG
);
469 * Sort the pathnames that matched.
471 qsort(ef
->result
.files
, ef
->result
.nfile
, sizeof(ef
->result
.files
[0]),
475 * Return the result container.
480 /*.......................................................................
481 * Attempt to recursively match the given pattern with the contents of
482 * the current directory, descending sub-directories as needed.
485 * ef ExpandFile * The pathname expansion resource object.
486 * dr DirReader * The directory reader object of the directory
488 * pattern const char * The pattern to match with files in the current
490 * separate int When appending a filename from the specified
491 * directory to ef->pathname, insert a directory
492 * separator between the existing pathname and
493 * the filename, unless separate is zero.
498 static int ef_match_relative_pathname(ExpandFile
*ef
, DirReader
*dr
,
499 const char *pattern
, int separate
)
501 const char *nextp
; /* The pointer to the character that follows the part */
502 /* of the pattern that is to be matched with files */
503 /* in the current directory. */
504 char *file
; /* The name of the file being matched */
505 int pathlen
; /* The length of ef->pathname[] on entry to this */
508 * Record the current length of the pathname string recorded in
511 pathlen
= strlen(ef
->path
->name
);
513 * Get a pointer to the character that follows the end of the part of
514 * the pattern that should be matched to files within the current directory.
515 * This will either point to a directory separator, or to the '\0' terminator
516 * of the pattern string.
518 for(nextp
=pattern
; *nextp
&& strncmp(nextp
, FS_DIR_SEP
, FS_DIR_SEP_LEN
) != 0;
522 * Read each file from the directory, attempting to match it to the
525 while((file
=_dr_next_file(dr
)) != NULL
) {
527 * Does the latest file match the pattern up to nextp?
529 if(ef_string_matches_pattern(file
, pattern
, file
[0]=='.', nextp
)) {
531 * Append the new directory entry to the current matching pathname.
533 if((separate
&& _pn_append_to_path(ef
->path
, FS_DIR_SEP
, -1, 0)==NULL
) ||
534 _pn_append_to_path(ef
->path
, file
, -1, 0)==NULL
) {
535 _err_record_msg(ef
->err
, "Insufficient memory to record path",
540 * If we have reached the end of the pattern, record the accumulated
541 * pathname in the list of matching files.
544 if(ef_record_pathname(ef
, ef
->path
->name
, 0))
547 * If the matching directory entry is a subdirectory, and the
548 * next character of the pattern is a directory separator,
549 * recursively call the current function to scan the sub-directory
552 } else if(_pu_path_is_dir(ef
->path
->name
) &&
553 strncmp(nextp
, FS_DIR_SEP
, FS_DIR_SEP_LEN
) == 0) {
555 * If the pattern finishes with the directory separator, then
556 * record the pathame as matching.
558 if(nextp
[FS_DIR_SEP_LEN
] == '\0') {
559 if(ef_record_pathname(ef
, ef
->path
->name
, 0))
562 * Match files within the directory.
565 DirNode
*subdnode
= ef_open_dir(ef
, ef
->path
->name
);
567 if(ef_match_relative_pathname(ef
, subdnode
->dr
,
568 nextp
+FS_DIR_SEP_LEN
, 1)) {
569 subdnode
= ef_close_dir(ef
, subdnode
);
572 subdnode
= ef_close_dir(ef
, subdnode
);
577 * Remove the latest filename from the pathname string, so that
578 * another matching file can be appended.
580 ef
->path
->name
[pathlen
] = '\0';
586 /*.......................................................................
587 * Record a new matching filename.
590 * ef ExpandFile * The filename-match resource object.
591 * pathname const char * The pathname to record.
592 * remove_escapes int If true, remove backslash escapes in the
593 * recorded copy of the pathname.
596 * 1 - Error (ef->err will contain a
597 * description of the error).
599 static int ef_record_pathname(ExpandFile
*ef
, const char *pathname
,
602 char *copy
; /* The recorded copy of pathname[] */
604 * Attempt to make a copy of the pathname in the cache.
606 copy
= ef_cache_pathname(ef
, pathname
, remove_escapes
);
610 * If there isn't room to record a pointer to the recorded pathname in the
611 * array of files, attempt to extend the array.
613 if(ef
->result
.nfile
+ 1 > ef
->files_dim
) {
614 int files_dim
= ef
->files_dim
+ MATCH_BLK_FACT
;
615 char **files
= (char **) reallocarray(ef
->result
.files
, files_dim
,
618 _err_record_msg(ef
->err
,
619 "Insufficient memory to record all of the matching filenames",
624 ef
->result
.files
= files
;
625 ef
->files_dim
= files_dim
;
628 * Record a pointer to the new match.
630 ef
->result
.files
[ef
->result
.nfile
++] = copy
;
634 /*.......................................................................
635 * Record a pathname in the cache.
638 * ef ExpandFile * The filename-match resource object.
639 * pathname char * The pathname to record.
640 * remove_escapes int If true, remove backslash escapes in the
641 * copy of the pathname.
643 * return char * The pointer to the copy of the pathname.
644 * On error NULL is returned and a description
645 * of the error is left in ef->err.
647 static char *ef_cache_pathname(ExpandFile
*ef
, const char *pathname
,
650 char *copy
= _sg_store_string(ef
->sg
, pathname
, remove_escapes
);
652 _err_record_msg(ef
->err
, "Insufficient memory to store pathname",
657 /*.......................................................................
658 * Clear the results of the previous expansion operation, ready for the
662 * ef ExpandFile * The pathname expansion resource object.
664 static void ef_clear_files(ExpandFile
*ef
)
666 _clr_StringGroup(ef
->sg
);
667 _pn_clear_path(ef
->path
);
668 ef
->result
.exists
= 0;
669 ef
->result
.nfile
= 0;
670 _err_clear_msg(ef
->err
);
674 /*.......................................................................
675 * Get a new directory reader object from the cache.
678 * ef ExpandFile * The pathname expansion resource object.
679 * pathname const char * The pathname of the directory.
681 * return DirNode * The cache entry of the new directory reader,
682 * or NULL on error. On error, ef->err will
683 * contain a description of the error.
685 static DirNode
*ef_open_dir(ExpandFile
*ef
, const char *pathname
)
687 char *errmsg
= NULL
; /* An error message from a called function */
688 DirNode
*node
; /* The cache node used */
690 * Get the directory reader cache.
692 DirCache
*cache
= &ef
->cache
;
694 * Extend the cache if there are no free cache nodes.
697 node
= (DirNode
*) _new_FreeListNode(cache
->mem
);
699 _err_record_msg(ef
->err
, "Insufficient memory to open a new directory",
704 * Initialize the cache node.
710 * Allocate a directory reader object.
712 node
->dr
= _new_DirReader();
714 _err_record_msg(ef
->err
, "Insufficient memory to open a new directory",
716 node
= (DirNode
*) _del_FreeListNode(cache
->mem
, node
);
720 * Append the node to the cache list.
722 node
->prev
= cache
->tail
;
724 cache
->tail
->next
= node
;
727 cache
->next
= cache
->tail
= node
;
730 * Get the first unused node, but don't remove it from the list yet.
734 * Attempt to open the specified directory.
736 if(_dr_open_dir(node
->dr
, pathname
, &errmsg
)) {
737 _err_record_msg(ef
->err
, errmsg
, END_ERR_MSG
);
741 * Now that we have successfully opened the specified directory,
742 * remove the cache node from the list, and relink the list around it.
744 cache
->next
= node
->next
;
746 node
->prev
->next
= node
->next
;
748 cache
->head
= node
->next
;
750 node
->next
->prev
= node
->prev
;
752 cache
->tail
= node
->prev
;
753 node
->next
= node
->prev
= NULL
;
755 * Return the successfully initialized cache node to the caller.
760 /*.......................................................................
761 * Return a directory reader object to the cache, after first closing
762 * the directory that it was managing.
765 * ef ExpandFile * The pathname expansion resource object.
766 * node DirNode * The cache entry of the directory reader, as returned
769 * return DirNode * The deleted DirNode (ie. allways NULL).
771 static DirNode
*ef_close_dir(ExpandFile
*ef
, DirNode
*node
)
774 * Get the directory reader cache.
776 DirCache
*cache
= &ef
->cache
;
778 * Close the directory.
780 _dr_close_dir(node
->dr
);
782 * Return the node to the tail of the cache list.
785 node
->prev
= cache
->tail
;
787 cache
->tail
->next
= node
;
789 cache
->head
= cache
->tail
= node
;
795 /*.......................................................................
796 * Return non-zero if the specified file name matches a given glob
800 * file const char * The file-name component to be matched to the pattern.
801 * pattern const char * The start of the pattern to match against file[].
802 * xplicit int If non-zero, the first character must be matched
803 * explicitly (ie. not with a wildcard).
804 * nextp const char * The pointer to the the character following the
805 * end of the pattern in pattern[].
807 * return int 0 - Doesn't match.
808 * 1 - The file-name string matches the pattern.
810 static int ef_string_matches_pattern(const char *file
, const char *pattern
,
811 int xplicit
, const char *nextp
)
813 const char *pptr
= pattern
; /* The pointer used to scan the pattern */
814 const char *fptr
= file
; /* The pointer used to scan the filename string */
816 * Match each character of the pattern in turn.
818 while(pptr
< nextp
) {
820 * Handle the next character of the pattern.
824 * A match zero-or-more characters wildcard operator.
828 * Skip the '*' character in the pattern.
832 * If wildcards aren't allowed, the pattern doesn't match.
837 * If the pattern ends with a the '*' wildcard, then the
838 * rest of the filename matches this.
843 * Using the wildcard to match successively longer sections of
844 * the remaining characters of the filename, attempt to match
845 * the tail of the filename against the tail of the pattern.
847 for( ; *fptr
; fptr
++) {
848 if(ef_string_matches_pattern(fptr
, pptr
, 0, nextp
))
851 return 0; /* The pattern following the '*' didn't match */
854 * A match-one-character wildcard operator.
858 * If there is a character to be matched, skip it and advance the
861 if(!xplicit
&& *fptr
) {
865 * If we hit the end of the filename string, there is no character
866 * matching the operator, so the string doesn't match.
873 * A character range operator, with the character ranges enclosed
874 * in matching square brackets.
877 if(xplicit
|| !ef_matches_range(*fptr
++, ++pptr
, &pptr
))
881 * A backslash in the pattern prevents the following character as
882 * being seen as a special character.
886 /* Note fallthrough to default */
888 * A normal character to be matched explicitly.
900 * After passing the first character, turn off the explicit match
906 * To get here the pattern must have been exhausted. If the filename
907 * string matched, then the filename string must also have been
910 return *fptr
== '\0';
913 /*.......................................................................
914 * Match a character range expression terminated by an unescaped close
918 * c int The character to be matched with the range
920 * pattern const char * The range pattern to be matched (ie. after the
921 * initiating '[' character).
922 * endp const char ** On output a pointer to the character following the
923 * range expression will be assigned to *endp.
925 * return int 0 - Doesn't match.
926 * 1 - The character matched.
928 static int ef_matches_range(int c
, const char *pattern
, const char **endp
)
930 const char *pptr
= pattern
; /* The pointer used to scan the pattern */
931 int invert
= 0; /* True to invert the sense of the match */
932 int matched
= 0; /* True if the character matched the pattern */
934 * If the first character is a caret, the sense of the match is
935 * inverted and only if the character isn't one of those in the
936 * range, do we say that it matches.
943 * The hyphen is only a special character when it follows the first
944 * character of the range (not including the caret).
953 * Skip other leading '-' characters since they make no sense.
959 * The hyphen is only a special character when it follows the first
960 * character of the range (not including the caret or a hyphen).
970 * Having dealt with the characters that have special meanings at
971 * the beginning of a character range expression, see if the
972 * character matches any of the remaining characters of the range,
973 * up until a terminating ']' character is seen.
975 while(!matched
&& *pptr
&& *pptr
!= ']') {
977 * Is this a range of characters signaled by the two end characters
978 * separated by a hyphen?
982 if(c
>= pptr
[-1] && c
<= pptr
[1])
987 * A normal character to be compared directly.
989 } else if(*pptr
++ == c
) {
994 * Find the terminating ']'.
996 while(*pptr
&& *pptr
!= ']')
999 * Did we find a terminating ']'?
1003 return matched
? !invert
: invert
;
1006 * If the pattern didn't end with a ']' then it doesn't match, regardless
1007 * of the value of the required sense of the match.
1013 /*.......................................................................
1014 * This is a qsort() comparison function used to sort strings.
1017 * v1, v2 void * Pointers to the two strings to be compared.
1019 * return int -1 -> v1 < v2.
1023 static int ef_cmp_strings(const void *v1
, const void *v2
)
1025 char * const *s1
= (char * const *) v1
;
1026 char * const *s2
= (char * const *) v2
;
1027 return strcmp(*s1
, *s2
);
1030 /*.......................................................................
1031 * Preprocess a path, expanding ~/, ~user/ and $envvar references, using
1032 * ef->path as a work buffer, then copy the result into a cache entry,
1033 * and return a pointer to this copy.
1036 * ef ExpandFile * The resource object of the file matcher.
1037 * pathlen int The length of the prefix of path[] to be expanded.
1039 * return char * A pointer to a copy of the output path in the
1040 * cache. On error NULL is returned, and a description
1041 * of the error is left in ef->err.
1043 static char *ef_expand_special(ExpandFile
*ef
, const char *path
, int pathlen
)
1045 int spos
; /* The index of the start of the path segment that needs */
1046 /* to be copied from path[] to the output pathname. */
1047 int ppos
; /* The index of a character in path[] */
1048 char *pptr
; /* A pointer into the output path */
1049 int escaped
; /* True if the previous character was a '\' */
1052 * Clear the pathname buffer.
1054 _pn_clear_path(ef
->path
);
1056 * We need to perform two passes, one to expand environment variables
1057 * and a second to do tilde expansion. This caters for the case
1058 * where an initial dollar expansion yields a tilde expression.
1061 for(spos
=ppos
=0; ppos
< pathlen
; ppos
++) {
1065 } else if(c
== '\\') {
1067 } else if(c
== '$') {
1068 int envlen
; /* The length of the environment variable */
1069 char *value
; /* The value of the environment variable */
1071 * Record the preceding unrecorded part of the pathname.
1073 if(spos
< ppos
&& _pn_append_to_path(ef
->path
, path
+ spos
, ppos
-spos
, 0)
1075 _err_record_msg(ef
->err
, "Insufficient memory to expand path",
1084 * Copy the environment variable name that follows the dollar into
1085 * ef->envnam[], stopping if a directory separator or end of string
1088 for(envlen
=0; envlen
<ENV_LEN
&& ppos
< pathlen
&&
1089 strncmp(path
+ ppos
, FS_DIR_SEP
, FS_DIR_SEP_LEN
); envlen
++)
1090 ef
->envnam
[envlen
] = path
[ppos
++];
1092 * If the username overflowed the buffer, treat it as invalid (note that
1093 * on most unix systems only 8 characters are allowed in a username,
1094 * whereas our ENV_LEN is much bigger than that.
1096 if(envlen
>= ENV_LEN
) {
1097 _err_record_msg(ef
->err
, "Environment variable name too long",
1102 * Terminate the environment variable name.
1104 ef
->envnam
[envlen
] = '\0';
1106 * Lookup the value of the environment variable.
1108 value
= getenv(ef
->envnam
);
1110 _err_record_msg(ef
->err
, "No expansion found for: $", ef
->envnam
,
1115 * Copy the value of the environment variable into the output pathname.
1117 if(_pn_append_to_path(ef
->path
, value
, -1, 0) == NULL
) {
1118 _err_record_msg(ef
->err
, "Insufficient memory to expand path",
1123 * Record the start of the uncopied tail of the input pathname.
1129 * Record the uncopied tail of the pathname.
1131 if(spos
< ppos
&& _pn_append_to_path(ef
->path
, path
+ spos
, ppos
-spos
, 0)
1133 _err_record_msg(ef
->err
, "Insufficient memory to expand path", END_ERR_MSG
);
1137 * If the first character of the resulting pathname is a tilde,
1138 * then attempt to substitute the home directory of the specified user.
1140 pptr
= ef
->path
->name
;
1141 if(*pptr
== '~' && path
[0] != '\\') {
1142 int usrlen
; /* The length of the username following the tilde */
1143 const char *homedir
; /* The home directory of the user */
1144 int homelen
; /* The length of the home directory string */
1145 int plen
; /* The current length of the path */
1146 int skip
=0; /* The number of characters to skip after the ~user */
1148 * Get the current length of the output path.
1150 plen
= strlen(ef
->path
->name
);
1156 * Copy the optional username that follows the tilde into ef->usrnam[].
1158 for(usrlen
=0; usrlen
<USR_LEN
&& *pptr
&&
1159 strncmp(pptr
, FS_DIR_SEP
, FS_DIR_SEP_LEN
); usrlen
++)
1160 ef
->usrnam
[usrlen
] = *pptr
++;
1162 * If the username overflowed the buffer, treat it as invalid (note that
1163 * on most unix systems only 8 characters are allowed in a username,
1164 * whereas our USR_LEN is much bigger than that.
1166 if(usrlen
>= USR_LEN
) {
1167 _err_record_msg(ef
->err
, "Username too long", END_ERR_MSG
);
1171 * Terminate the username string.
1173 ef
->usrnam
[usrlen
] = '\0';
1175 * Lookup the home directory of the user.
1177 homedir
= _hd_lookup_home_dir(ef
->home
, ef
->usrnam
);
1179 _err_record_msg(ef
->err
, _hd_last_home_dir_error(ef
->home
), END_ERR_MSG
);
1182 homelen
= strlen(homedir
);
1184 * ~user and ~ are usually followed by a directory separator to
1185 * separate them from the file contained in the home directory.
1186 * If the home directory is the root directory, then we don't want
1187 * to follow the home directory by a directory separator, so we must
1190 if(strcmp(homedir
, FS_ROOT_DIR
) == 0 &&
1191 strncmp(pptr
, FS_DIR_SEP
, FS_DIR_SEP_LEN
) == 0) {
1192 skip
= FS_DIR_SEP_LEN
;
1195 * If needed, increase the size of the pathname buffer to allow it
1196 * to accomodate the home directory instead of the tilde expression.
1197 * Note that pptr may not be valid after this call.
1199 if(_pn_resize_path(ef
->path
, plen
- usrlen
- 1 - skip
+ homelen
)==NULL
) {
1200 _err_record_msg(ef
->err
, "Insufficient memory to expand filename",
1205 * Move the part of the pathname that follows the tilde expression to
1206 * the end of where the home directory will need to be inserted.
1208 memmove(ef
->path
->name
+ homelen
,
1209 ef
->path
->name
+ 1 + usrlen
+ skip
, plen
- usrlen
- 1 - skip
+1);
1211 * Write the home directory at the beginning of the string.
1213 for(i
=0; i
<homelen
; i
++)
1214 ef
->path
->name
[i
] = homedir
[i
];
1217 * Copy the result into the cache, and return a pointer to the copy.
1219 return ef_cache_pathname(ef
, ef
->path
->name
, 0);
1222 /*.......................................................................
1223 * Return a description of the last path-expansion error that occurred.
1226 * ef ExpandFile * The path-expansion resource object.
1228 * return char * The description of the last error.
1230 const char *ef_last_error(ExpandFile
*ef
)
1232 return ef
? _err_get_msg(ef
->err
) : "NULL ExpandFile argument";
1235 /*.......................................................................
1236 * Print out an array of matching files.
1239 * result FileExpansion * The container of the sorted array of
1241 * fp FILE * The output stream to write to.
1242 * term_width int The width of the terminal.
1244 * return int 0 - OK.
1247 int ef_list_expansions(FileExpansion
*result
, FILE *fp
, int term_width
)
1249 return _ef_output_expansions(result
, _io_write_stdio
, fp
, term_width
);
1252 /*.......................................................................
1253 * Print out an array of matching files via a callback.
1256 * result FileExpansion * The container of the sorted array of
1258 * write_fn GlWriteFn * The function to call to write the
1259 * expansions or 0 to discard the output.
1260 * data void * Anonymous data to pass to write_fn().
1261 * term_width int The width of the terminal.
1263 * return int 0 - OK.
1266 int _ef_output_expansions(FileExpansion
*result
, GlWriteFn
*write_fn
,
1267 void *data
, int term_width
)
1269 EfListFormat fmt
; /* List formatting information */
1270 int lnum
; /* The sequential number of the line to print next */
1272 * Not enough space to list anything?
1277 * Do we have a callback to write via, and any expansions to be listed?
1279 if(write_fn
&& result
&& result
->nfile
>0) {
1281 * Work out how to arrange the listing into fixed sized columns.
1283 ef_plan_listing(result
, term_width
, &fmt
);
1285 * Print the listing to the specified stream.
1287 for(lnum
=0; lnum
< fmt
.nline
; lnum
++) {
1288 if(ef_format_line(result
, &fmt
, lnum
, write_fn
, data
))
1295 /*.......................................................................
1296 * Work out how to arrange a given array of completions into a listing
1297 * of one or more fixed size columns.
1300 * result FileExpansion * The set of completions to be listed.
1301 * term_width int The width of the terminal. A lower limit of
1302 * zero is quietly enforced.
1304 * fmt EfListFormat * The formatting information will be assigned
1305 * to the members of *fmt.
1307 static void ef_plan_listing(FileExpansion
*result
, int term_width
,
1310 int maxlen
; /* The length of the longest matching string */
1313 * Ensure that term_width >= 0.
1318 * Start by assuming the worst case, that either nothing will fit
1319 * on the screen, or that there are no matches to be listed.
1321 fmt
->term_width
= term_width
;
1322 fmt
->column_width
= 0;
1323 fmt
->nline
= fmt
->ncol
= 0;
1325 * Work out the maximum length of the matching strings.
1328 for(i
=0; i
<result
->nfile
; i
++) {
1329 int len
= strlen(result
->files
[i
]);
1339 * Split the available terminal width into columns of
1340 * maxlen + EF_COL_SEP characters.
1342 fmt
->column_width
= maxlen
;
1343 fmt
->ncol
= fmt
->term_width
/ (fmt
->column_width
+ EF_COL_SEP
);
1345 * If the column width is greater than the terminal width, zero columns
1346 * will have been selected. Set a lower limit of one column. Leave it
1347 * up to the caller how to deal with completions who's widths exceed
1348 * the available terminal width.
1353 * How many lines of output will be needed?
1355 fmt
->nline
= (result
->nfile
+ fmt
->ncol
- 1) / fmt
->ncol
;
1359 /*.......................................................................
1360 * Render one line of a multi-column listing of completions, using a
1361 * callback function to pass the output to an arbitrary destination.
1364 * result FileExpansion * The container of the sorted array of
1366 * fmt EfListFormat * Formatting information.
1367 * lnum int The index of the line to print, starting
1368 * from 0, and incrementing until the return
1369 * value indicates that there is nothing more
1371 * write_fn GlWriteFn * The function to call to write the line, or
1372 * 0 to discard the output.
1373 * data void * Anonymous data to pass to write_fn().
1375 * return int 0 - Line printed ok.
1376 * 1 - Nothing to print.
1378 static int ef_format_line(FileExpansion
*result
, EfListFormat
*fmt
, int lnum
,
1379 GlWriteFn
*write_fn
, void *data
)
1381 int col
; /* The index of the list column being output */
1383 * If the line index is out of bounds, there is nothing to be written.
1385 if(lnum
< 0 || lnum
>= fmt
->nline
)
1388 * If no output function has been provided, return as though the line
1394 * Print the matches in 'ncol' columns, sorted in line order within each
1397 for(col
=0; col
< fmt
->ncol
; col
++) {
1398 int m
= col
*fmt
->nline
+ lnum
;
1400 * Is there another match to be written? Note that in general
1401 * the last line of a listing will have fewer filled columns
1402 * than the initial lines.
1404 if(m
< result
->nfile
) {
1405 char *file
= result
->files
[m
];
1407 * How long are the completion and type-suffix strings?
1409 int flen
= strlen(file
);
1411 * Write the completion string.
1413 if(write_fn(data
, file
, flen
) != flen
)
1416 * If another column follows the current one, pad to its start with spaces.
1418 if(col
+1 < fmt
->ncol
) {
1420 * The following constant string of spaces is used to pad the output.
1422 static const char spaces
[] = " ";
1423 static const int nspace
= sizeof(spaces
) - 1;
1425 * Pad to the next column, using as few sub-strings of the spaces[]
1426 * array as possible.
1428 int npad
= fmt
->column_width
+ EF_COL_SEP
- flen
;
1430 int n
= npad
> nspace
? nspace
: npad
;
1431 if(write_fn(data
, spaces
+ nspace
- n
, n
) != n
)
1444 if(write_fn(data
, s
, n
) != n
)
1450 #endif /* ifndef WITHOUT_FILE_SYSTEM */