17c478bd9Sstevel@tonic-gate /*
27c478bd9Sstevel@tonic-gate  * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd.
3*55fea89dSDan Cross  *
47c478bd9Sstevel@tonic-gate  * All rights reserved.
5*55fea89dSDan Cross  *
67c478bd9Sstevel@tonic-gate  * Permission is hereby granted, free of charge, to any person obtaining a
77c478bd9Sstevel@tonic-gate  * copy of this software and associated documentation files (the
87c478bd9Sstevel@tonic-gate  * "Software"), to deal in the Software without restriction, including
97c478bd9Sstevel@tonic-gate  * without limitation the rights to use, copy, modify, merge, publish,
107c478bd9Sstevel@tonic-gate  * distribute, and/or sell copies of the Software, and to permit persons
117c478bd9Sstevel@tonic-gate  * to whom the Software is furnished to do so, provided that the above
127c478bd9Sstevel@tonic-gate  * copyright notice(s) and this permission notice appear in all copies of
137c478bd9Sstevel@tonic-gate  * the Software and that both the above copyright notice(s) and this
147c478bd9Sstevel@tonic-gate  * permission notice appear in supporting documentation.
15*55fea89dSDan Cross  *
167c478bd9Sstevel@tonic-gate  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
177c478bd9Sstevel@tonic-gate  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
187c478bd9Sstevel@tonic-gate  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
197c478bd9Sstevel@tonic-gate  * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
207c478bd9Sstevel@tonic-gate  * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL
217c478bd9Sstevel@tonic-gate  * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING
227c478bd9Sstevel@tonic-gate  * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
237c478bd9Sstevel@tonic-gate  * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
247c478bd9Sstevel@tonic-gate  * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
25*55fea89dSDan Cross  *
267c478bd9Sstevel@tonic-gate  * Except as contained in this notice, the name of a copyright holder
277c478bd9Sstevel@tonic-gate  * shall not be used in advertising or otherwise to promote the sale, use
287c478bd9Sstevel@tonic-gate  * or other dealings in this Software without prior written authorization
297c478bd9Sstevel@tonic-gate  * of the copyright holder.
307c478bd9Sstevel@tonic-gate  */
317c478bd9Sstevel@tonic-gate 
327c478bd9Sstevel@tonic-gate /*
337c478bd9Sstevel@tonic-gate  * If file-system access is to be excluded, this module has no function,
347c478bd9Sstevel@tonic-gate  * so all of its code should be excluded.
357c478bd9Sstevel@tonic-gate  */
367c478bd9Sstevel@tonic-gate #ifndef WITHOUT_FILE_SYSTEM
377c478bd9Sstevel@tonic-gate 
387c478bd9Sstevel@tonic-gate #include <stdio.h>
397c478bd9Sstevel@tonic-gate #include <stdlib.h>
407c478bd9Sstevel@tonic-gate #include <string.h>
417c478bd9Sstevel@tonic-gate #include <errno.h>
427c478bd9Sstevel@tonic-gate 
437c478bd9Sstevel@tonic-gate #include "freelist.h"
447c478bd9Sstevel@tonic-gate #include "direader.h"
457c478bd9Sstevel@tonic-gate #include "pathutil.h"
467c478bd9Sstevel@tonic-gate #include "homedir.h"
477c478bd9Sstevel@tonic-gate #include "stringrp.h"
487c478bd9Sstevel@tonic-gate #include "libtecla.h"
497c478bd9Sstevel@tonic-gate #include "ioutil.h"
507c478bd9Sstevel@tonic-gate #include "expand.h"
517c478bd9Sstevel@tonic-gate #include "errmsg.h"
527c478bd9Sstevel@tonic-gate 
537c478bd9Sstevel@tonic-gate /*
547c478bd9Sstevel@tonic-gate  * Specify the number of elements to extend the files[] array by
557c478bd9Sstevel@tonic-gate  * when it proves to be too small. This also sets the initial size
567c478bd9Sstevel@tonic-gate  * of the array.
577c478bd9Sstevel@tonic-gate  */
587c478bd9Sstevel@tonic-gate #define MATCH_BLK_FACT 256
597c478bd9Sstevel@tonic-gate 
607c478bd9Sstevel@tonic-gate /*
617c478bd9Sstevel@tonic-gate  * A list of directory iterators is maintained using nodes of the
627c478bd9Sstevel@tonic-gate  * following form.
637c478bd9Sstevel@tonic-gate  */
647c478bd9Sstevel@tonic-gate typedef struct DirNode DirNode;
657c478bd9Sstevel@tonic-gate struct DirNode {
667c478bd9Sstevel@tonic-gate   DirNode *next;       /* The next directory in the list */
677c478bd9Sstevel@tonic-gate   DirNode *prev;       /* The node that precedes this node in the list */
687c478bd9Sstevel@tonic-gate   DirReader *dr;       /* The directory reader object */
697c478bd9Sstevel@tonic-gate };
707c478bd9Sstevel@tonic-gate 
717c478bd9Sstevel@tonic-gate typedef struct {
727c478bd9Sstevel@tonic-gate   FreeList *mem;       /* Memory for DirNode list nodes */
737c478bd9Sstevel@tonic-gate   DirNode *head;       /* The head of the list of used and unused cache nodes */
747c478bd9Sstevel@tonic-gate   DirNode *next;       /* The next unused node between head and tail */
757c478bd9Sstevel@tonic-gate   DirNode *tail;       /* The tail of the list of unused cache nodes */
767c478bd9Sstevel@tonic-gate } DirCache;
777c478bd9Sstevel@tonic-gate 
787c478bd9Sstevel@tonic-gate /*
797c478bd9Sstevel@tonic-gate  * Specify how many directory cache nodes to allocate at a time.
807c478bd9Sstevel@tonic-gate  */
817c478bd9Sstevel@tonic-gate #define DIR_CACHE_BLK 20
827c478bd9Sstevel@tonic-gate 
837c478bd9Sstevel@tonic-gate /*
847c478bd9Sstevel@tonic-gate  * Set the maximum length allowed for usernames.
857c478bd9Sstevel@tonic-gate  */
867c478bd9Sstevel@tonic-gate #define USR_LEN 100
877c478bd9Sstevel@tonic-gate 
887c478bd9Sstevel@tonic-gate /*
897c478bd9Sstevel@tonic-gate  * Set the maximum length allowed for environment variable names.
907c478bd9Sstevel@tonic-gate  */
917c478bd9Sstevel@tonic-gate #define ENV_LEN 100
927c478bd9Sstevel@tonic-gate 
937c478bd9Sstevel@tonic-gate /*
947c478bd9Sstevel@tonic-gate  * Set the default number of spaces place between columns when listing
957c478bd9Sstevel@tonic-gate  * a set of expansions.
967c478bd9Sstevel@tonic-gate  */
977c478bd9Sstevel@tonic-gate #define EF_COL_SEP 2
987c478bd9Sstevel@tonic-gate 
997c478bd9Sstevel@tonic-gate struct ExpandFile {
1007c478bd9Sstevel@tonic-gate   ErrMsg *err;            /* The error reporting buffer */
1017c478bd9Sstevel@tonic-gate   StringGroup *sg;        /* A list of string segments in which */
1027c478bd9Sstevel@tonic-gate                           /*  matching filenames are stored. */
1037c478bd9Sstevel@tonic-gate   DirCache cache;         /* The cache of directory reader objects */
1047c478bd9Sstevel@tonic-gate   PathName *path;         /* The pathname being matched */
1057c478bd9Sstevel@tonic-gate   HomeDir *home;          /* Home-directory lookup object */
1067c478bd9Sstevel@tonic-gate   int files_dim;          /* The allocated dimension of result.files[] */
1077c478bd9Sstevel@tonic-gate   char usrnam[USR_LEN+1]; /* A user name */
1087c478bd9Sstevel@tonic-gate   char envnam[ENV_LEN+1]; /* An environment variable name */
1097c478bd9Sstevel@tonic-gate   FileExpansion result;   /* The container used to return the results of */
1107c478bd9Sstevel@tonic-gate                           /*  expanding a path. */
1117c478bd9Sstevel@tonic-gate };
1127c478bd9Sstevel@tonic-gate 
1137c478bd9Sstevel@tonic-gate static int ef_record_pathname(ExpandFile *ef, const char *pathname,
1147c478bd9Sstevel@tonic-gate 			      int remove_escapes);
1157c478bd9Sstevel@tonic-gate static char *ef_cache_pathname(ExpandFile *ef, const char *pathname,
1167c478bd9Sstevel@tonic-gate 			       int remove_escapes);
1177c478bd9Sstevel@tonic-gate static void ef_clear_files(ExpandFile *ef);
1187c478bd9Sstevel@tonic-gate 
1197c478bd9Sstevel@tonic-gate static DirNode *ef_open_dir(ExpandFile *ef, const char *pathname);
1207c478bd9Sstevel@tonic-gate static DirNode *ef_close_dir(ExpandFile *ef, DirNode *node);
1217c478bd9Sstevel@tonic-gate static char *ef_expand_special(ExpandFile *ef, const char *path, int pathlen);
1227c478bd9Sstevel@tonic-gate static int ef_match_relative_pathname(ExpandFile *ef, DirReader *dr,
1237c478bd9Sstevel@tonic-gate 				      const char *pattern, int separate);
1247c478bd9Sstevel@tonic-gate static int ef_matches_range(int c, const char *pattern, const char **endp);
1257c478bd9Sstevel@tonic-gate static int ef_string_matches_pattern(const char *file, const char *pattern,
1267c478bd9Sstevel@tonic-gate 				      int xplicit, const char *nextp);
1277c478bd9Sstevel@tonic-gate static int ef_cmp_strings(const void *v1, const void *v2);
1287c478bd9Sstevel@tonic-gate 
1297c478bd9Sstevel@tonic-gate /*
1307c478bd9Sstevel@tonic-gate  * Encapsulate the formatting information needed to layout a
1317c478bd9Sstevel@tonic-gate  * multi-column listing of expansions.
1327c478bd9Sstevel@tonic-gate  */
1337c478bd9Sstevel@tonic-gate typedef struct {
1347c478bd9Sstevel@tonic-gate   int term_width;     /* The width of the terminal (characters) */
1357c478bd9Sstevel@tonic-gate   int column_width;   /* The number of characters within in each column. */
1367c478bd9Sstevel@tonic-gate   int ncol;           /* The number of columns needed */
1377c478bd9Sstevel@tonic-gate   int nline;          /* The number of lines needed */
1387c478bd9Sstevel@tonic-gate } EfListFormat;
1397c478bd9Sstevel@tonic-gate 
1407c478bd9Sstevel@tonic-gate /*
1417c478bd9Sstevel@tonic-gate  * Given the current terminal width, and a list of file expansions,
1427c478bd9Sstevel@tonic-gate  * determine how to best use the terminal width to display a multi-column
1437c478bd9Sstevel@tonic-gate  * listing of expansions.
1447c478bd9Sstevel@tonic-gate  */
1457c478bd9Sstevel@tonic-gate static void ef_plan_listing(FileExpansion *result, int term_width,
1467c478bd9Sstevel@tonic-gate 			    EfListFormat *fmt);
1477c478bd9Sstevel@tonic-gate 
1487c478bd9Sstevel@tonic-gate /*
1497c478bd9Sstevel@tonic-gate  * Display a given line of a multi-column list of file-expansions.
1507c478bd9Sstevel@tonic-gate  */
1517c478bd9Sstevel@tonic-gate static int ef_format_line(FileExpansion *result, EfListFormat *fmt, int lnum,
1527c478bd9Sstevel@tonic-gate 			  GlWriteFn *write_fn, void *data);
1537c478bd9Sstevel@tonic-gate 
1547c478bd9Sstevel@tonic-gate /*.......................................................................
1557c478bd9Sstevel@tonic-gate  * Create the resources needed to expand filenames.
1567c478bd9Sstevel@tonic-gate  *
1577c478bd9Sstevel@tonic-gate  * Output:
1587c478bd9Sstevel@tonic-gate  *  return  ExpandFile *  The new object, or NULL on error.
1597c478bd9Sstevel@tonic-gate  */
new_ExpandFile(void)1607c478bd9Sstevel@tonic-gate ExpandFile *new_ExpandFile(void)
1617c478bd9Sstevel@tonic-gate {
1627c478bd9Sstevel@tonic-gate   ExpandFile *ef;  /* The object to be returned */
1637c478bd9Sstevel@tonic-gate /*
1647c478bd9Sstevel@tonic-gate  * Allocate the container.
1657c478bd9Sstevel@tonic-gate  */
1667c478bd9Sstevel@tonic-gate   ef = (ExpandFile *) malloc(sizeof(ExpandFile));
1677c478bd9Sstevel@tonic-gate   if(!ef) {
1687c478bd9Sstevel@tonic-gate     errno = ENOMEM;
1697c478bd9Sstevel@tonic-gate     return NULL;
1707c478bd9Sstevel@tonic-gate   };
1717c478bd9Sstevel@tonic-gate /*
1727c478bd9Sstevel@tonic-gate  * Before attempting any operation that might fail, initialize the
1737c478bd9Sstevel@tonic-gate  * container at least up to the point at which it can safely be passed
1747c478bd9Sstevel@tonic-gate  * to del_ExpandFile().
1757c478bd9Sstevel@tonic-gate  */
1767c478bd9Sstevel@tonic-gate   ef->err = NULL;
1777c478bd9Sstevel@tonic-gate   ef->sg = NULL;
1787c478bd9Sstevel@tonic-gate   ef->cache.mem = NULL;
1797c478bd9Sstevel@tonic-gate   ef->cache.head = NULL;
1807c478bd9Sstevel@tonic-gate   ef->cache.next = NULL;
1817c478bd9Sstevel@tonic-gate   ef->cache.tail = NULL;
1827c478bd9Sstevel@tonic-gate   ef->path = NULL;
1837c478bd9Sstevel@tonic-gate   ef->home = NULL;
1847c478bd9Sstevel@tonic-gate   ef->result.files = NULL;
1857c478bd9Sstevel@tonic-gate   ef->result.nfile = 0;
1867c478bd9Sstevel@tonic-gate   ef->usrnam[0] = '\0';
1877c478bd9Sstevel@tonic-gate   ef->envnam[0] = '\0';
1887c478bd9Sstevel@tonic-gate /*
1897c478bd9Sstevel@tonic-gate  * Allocate a place to record error messages.
1907c478bd9Sstevel@tonic-gate  */
1917c478bd9Sstevel@tonic-gate   ef->err = _new_ErrMsg();
1927c478bd9Sstevel@tonic-gate   if(!ef->err)
1937c478bd9Sstevel@tonic-gate     return del_ExpandFile(ef);
1947c478bd9Sstevel@tonic-gate /*
1957c478bd9Sstevel@tonic-gate  * Allocate a list of string segments for storing filenames.
1967c478bd9Sstevel@tonic-gate  */
1977c478bd9Sstevel@tonic-gate   ef->sg = _new_StringGroup(_pu_pathname_dim());
1987c478bd9Sstevel@tonic-gate   if(!ef->sg)
1997c478bd9Sstevel@tonic-gate     return del_ExpandFile(ef);
2007c478bd9Sstevel@tonic-gate /*
2017c478bd9Sstevel@tonic-gate  * Allocate a freelist for allocating directory cache nodes.
2027c478bd9Sstevel@tonic-gate  */
2037c478bd9Sstevel@tonic-gate   ef->cache.mem = _new_FreeList(sizeof(DirNode), DIR_CACHE_BLK);
2047c478bd9Sstevel@tonic-gate   if(!ef->cache.mem)
2057c478bd9Sstevel@tonic-gate     return del_ExpandFile(ef);
2067c478bd9Sstevel@tonic-gate /*
2077c478bd9Sstevel@tonic-gate  * Allocate a pathname buffer.
2087c478bd9Sstevel@tonic-gate  */
2097c478bd9Sstevel@tonic-gate   ef->path = _new_PathName();
2107c478bd9Sstevel@tonic-gate   if(!ef->path)
2117c478bd9Sstevel@tonic-gate     return del_ExpandFile(ef);
2127c478bd9Sstevel@tonic-gate /*
2137c478bd9Sstevel@tonic-gate  * Allocate an object for looking up home-directories.
2147c478bd9Sstevel@tonic-gate  */
2157c478bd9Sstevel@tonic-gate   ef->home = _new_HomeDir();
2167c478bd9Sstevel@tonic-gate   if(!ef->home)
2177c478bd9Sstevel@tonic-gate     return del_ExpandFile(ef);
2187c478bd9Sstevel@tonic-gate /*
2197c478bd9Sstevel@tonic-gate  * Allocate an array for files. This will be extended later if needed.
2207c478bd9Sstevel@tonic-gate  */
2217c478bd9Sstevel@tonic-gate   ef->files_dim = MATCH_BLK_FACT;
2227c478bd9Sstevel@tonic-gate   ef->result.files = (char **) malloc(sizeof(ef->result.files[0]) *
2237c478bd9Sstevel@tonic-gate 				      ef->files_dim);
2247c478bd9Sstevel@tonic-gate   if(!ef->result.files) {
2257c478bd9Sstevel@tonic-gate     errno = ENOMEM;
2267c478bd9Sstevel@tonic-gate     return del_ExpandFile(ef);
2277c478bd9Sstevel@tonic-gate   };
2287c478bd9Sstevel@tonic-gate   return ef;
2297c478bd9Sstevel@tonic-gate }
2307c478bd9Sstevel@tonic-gate 
2317c478bd9Sstevel@tonic-gate /*.......................................................................
2327c478bd9Sstevel@tonic-gate  * Delete a ExpandFile object.
2337c478bd9Sstevel@tonic-gate  *
2347c478bd9Sstevel@tonic-gate  * Input:
2357c478bd9Sstevel@tonic-gate  *  ef     ExpandFile *  The object to be deleted.
2367c478bd9Sstevel@tonic-gate  * Output:
2377c478bd9Sstevel@tonic-gate  *  return ExpandFile *  The deleted object (always NULL).
2387c478bd9Sstevel@tonic-gate  */
del_ExpandFile(ExpandFile * ef)2397c478bd9Sstevel@tonic-gate ExpandFile *del_ExpandFile(ExpandFile *ef)
2407c478bd9Sstevel@tonic-gate {
2417c478bd9Sstevel@tonic-gate   if(ef) {
2427c478bd9Sstevel@tonic-gate     DirNode *dnode;
2437c478bd9Sstevel@tonic-gate /*
2447c478bd9Sstevel@tonic-gate  * Delete the string segments.
2457c478bd9Sstevel@tonic-gate  */
2467c478bd9Sstevel@tonic-gate     ef->sg = _del_StringGroup(ef->sg);
2477c478bd9Sstevel@tonic-gate /*
2487c478bd9Sstevel@tonic-gate  * Delete the cached directory readers.
2497c478bd9Sstevel@tonic-gate  */
2507c478bd9Sstevel@tonic-gate     for(dnode=ef->cache.head; dnode; dnode=dnode->next)
2517c478bd9Sstevel@tonic-gate       dnode->dr = _del_DirReader(dnode->dr);
2527c478bd9Sstevel@tonic-gate /*
2537c478bd9Sstevel@tonic-gate  * Delete the memory from which the DirNode list was allocated, thus
2547c478bd9Sstevel@tonic-gate  * deleting the list at the same time.
2557c478bd9Sstevel@tonic-gate  */
2567c478bd9Sstevel@tonic-gate     ef->cache.mem = _del_FreeList(ef->cache.mem, 1);
2577c478bd9Sstevel@tonic-gate     ef->cache.head = ef->cache.tail = ef->cache.next = NULL;
2587c478bd9Sstevel@tonic-gate /*
2597c478bd9Sstevel@tonic-gate  * Delete the pathname buffer.
2607c478bd9Sstevel@tonic-gate  */
2617c478bd9Sstevel@tonic-gate     ef->path = _del_PathName(ef->path);
2627c478bd9Sstevel@tonic-gate /*
2637c478bd9Sstevel@tonic-gate  * Delete the home-directory lookup object.
2647c478bd9Sstevel@tonic-gate  */
2657c478bd9Sstevel@tonic-gate     ef->home = _del_HomeDir(ef->home);
2667c478bd9Sstevel@tonic-gate /*
2677c478bd9Sstevel@tonic-gate  * Delete the array of pointers to files.
2687c478bd9Sstevel@tonic-gate  */
2697c478bd9Sstevel@tonic-gate     if(ef->result.files) {
2707c478bd9Sstevel@tonic-gate       free(ef->result.files);
2717c478bd9Sstevel@tonic-gate       ef->result.files = NULL;
2727c478bd9Sstevel@tonic-gate     };
2737c478bd9Sstevel@tonic-gate /*
2747c478bd9Sstevel@tonic-gate  * Delete the error report buffer.
2757c478bd9Sstevel@tonic-gate  */
2767c478bd9Sstevel@tonic-gate     ef->err = _del_ErrMsg(ef->err);
2777c478bd9Sstevel@tonic-gate /*
2787c478bd9Sstevel@tonic-gate  * Delete the container.
2797c478bd9Sstevel@tonic-gate  */
2807c478bd9Sstevel@tonic-gate     free(ef);
2817c478bd9Sstevel@tonic-gate   };
2827c478bd9Sstevel@tonic-gate   return NULL;
2837c478bd9Sstevel@tonic-gate }
2847c478bd9Sstevel@tonic-gate 
2857c478bd9Sstevel@tonic-gate /*.......................................................................
2867c478bd9Sstevel@tonic-gate  * Expand a pathname, converting ~user/ and ~/ patterns at the start
2877c478bd9Sstevel@tonic-gate  * of the pathname to the corresponding home directories, replacing
2887c478bd9Sstevel@tonic-gate  * $envvar with the value of the corresponding environment variable,
2897c478bd9Sstevel@tonic-gate  * and then, if there are any wildcards, matching these against existing
2907c478bd9Sstevel@tonic-gate  * filenames.
2917c478bd9Sstevel@tonic-gate  *
2927c478bd9Sstevel@tonic-gate  * If no errors occur, a container is returned containing the array of
2937c478bd9Sstevel@tonic-gate  * files that resulted from the expansion. If there were no wildcards
2947c478bd9Sstevel@tonic-gate  * in the input pathname, this will contain just the original pathname
2957c478bd9Sstevel@tonic-gate  * after expansion of ~ and $ expressions. If there were any wildcards,
2967c478bd9Sstevel@tonic-gate  * then the array will contain the files that matched them. Note that
2977c478bd9Sstevel@tonic-gate  * if there were any wildcards but no existing files match them, this
2987c478bd9Sstevel@tonic-gate  * is counted as an error and NULL is returned.
2997c478bd9Sstevel@tonic-gate  *
3007c478bd9Sstevel@tonic-gate  * The supported wildcards and their meanings are:
3017c478bd9Sstevel@tonic-gate  *  *        -  Match any sequence of zero or more characters.
3027c478bd9Sstevel@tonic-gate  *  ?        -  Match any single character.
3037c478bd9Sstevel@tonic-gate  *  [chars]  -  Match any single character that appears in 'chars'.
3047c478bd9Sstevel@tonic-gate  *              If 'chars' contains an expression of the form a-b,
3057c478bd9Sstevel@tonic-gate  *              then any character between a and b, including a and b,
3067c478bd9Sstevel@tonic-gate  *              matches. The '-' character looses its special meaning
3077c478bd9Sstevel@tonic-gate  *              as a range specifier when it appears at the start
3087c478bd9Sstevel@tonic-gate  *              of the sequence of characters.
3097c478bd9Sstevel@tonic-gate  *  [^chars] -  The same as [chars] except that it matches any single
3107c478bd9Sstevel@tonic-gate  *              character that doesn't appear in 'chars'.
3117c478bd9Sstevel@tonic-gate  *
3127c478bd9Sstevel@tonic-gate  * Wildcard expressions are applied to individual filename components.
3137c478bd9Sstevel@tonic-gate  * They don't match across directory separators. A '.' character at
3147c478bd9Sstevel@tonic-gate  * the beginning of a filename component must also be matched
3157c478bd9Sstevel@tonic-gate  * explicitly by a '.' character in the input pathname, since these
3167c478bd9Sstevel@tonic-gate  * are UNIX's hidden files.
3177c478bd9Sstevel@tonic-gate  *
3187c478bd9Sstevel@tonic-gate  * Input:
3197c478bd9Sstevel@tonic-gate  *  ef         ExpandFile *  The pathname expansion resource object.
3207c478bd9Sstevel@tonic-gate  *  path             char *  The path name to be expanded.
3217c478bd9Sstevel@tonic-gate  *  pathlen           int    The length of the suffix of path[] that
3227c478bd9Sstevel@tonic-gate  *                           constitutes the filename to be expanded,
3237c478bd9Sstevel@tonic-gate  *                           or -1 to specify that the whole of the
3247c478bd9Sstevel@tonic-gate  *                           path string should be used. Note that
3257c478bd9Sstevel@tonic-gate  *                           regardless of the value of this argument,
3267c478bd9Sstevel@tonic-gate  *                           path[] must contain a '\0' terminated
3277c478bd9Sstevel@tonic-gate  *                           string, since this function checks that
3287c478bd9Sstevel@tonic-gate  *                           pathlen isn't mistakenly too long.
3297c478bd9Sstevel@tonic-gate  * Output:
3307c478bd9Sstevel@tonic-gate  *  return  FileExpansion *  A pointer to a container within the given
3317c478bd9Sstevel@tonic-gate  *                           ExpandFile object. This contains an array
3327c478bd9Sstevel@tonic-gate  *                           of the pathnames that resulted from expanding
3337c478bd9Sstevel@tonic-gate  *                           ~ and $ expressions and from matching any
3347c478bd9Sstevel@tonic-gate  *                           wildcards, sorted into lexical order.
3357c478bd9Sstevel@tonic-gate  *                           This container and its contents will be
3367c478bd9Sstevel@tonic-gate  *                           recycled on subsequent calls, so if you need
3377c478bd9Sstevel@tonic-gate  *                           to keep the results of two successive runs,
3387c478bd9Sstevel@tonic-gate  *                           you will either have to allocate a private
3397c478bd9Sstevel@tonic-gate  *                           copy of the array, or use two ExpandFile
3407c478bd9Sstevel@tonic-gate  *                           objects.
3417c478bd9Sstevel@tonic-gate  *
3427c478bd9Sstevel@tonic-gate  *                           On error NULL is returned. A description
3437c478bd9Sstevel@tonic-gate  *                           of the error can be acquired by calling the
3447c478bd9Sstevel@tonic-gate  *                           ef_last_error() function.
3457c478bd9Sstevel@tonic-gate  */
ef_expand_file(ExpandFile * ef,const char * path,int pathlen)3467c478bd9Sstevel@tonic-gate FileExpansion *ef_expand_file(ExpandFile *ef, const char *path, int pathlen)
3477c478bd9Sstevel@tonic-gate {
3487c478bd9Sstevel@tonic-gate   DirNode *dnode;       /* A directory-reader cache node */
3497c478bd9Sstevel@tonic-gate   const char *dirname;  /* The name of the top level directory of the search */
3507c478bd9Sstevel@tonic-gate   const char *pptr;     /* A pointer into path[] */
3517c478bd9Sstevel@tonic-gate   int wild;             /* True if the path contains any wildcards */
3527c478bd9Sstevel@tonic-gate /*
3537c478bd9Sstevel@tonic-gate  * Check the arguments.
3547c478bd9Sstevel@tonic-gate  */
3557c478bd9Sstevel@tonic-gate   if(!ef || !path) {
3567c478bd9Sstevel@tonic-gate     if(ef) {
3577c478bd9Sstevel@tonic-gate       _err_record_msg(ef->err, "ef_expand_file: NULL path argument",
3587c478bd9Sstevel@tonic-gate 		      END_ERR_MSG);
3597c478bd9Sstevel@tonic-gate     };
3607c478bd9Sstevel@tonic-gate     errno = EINVAL;
3617c478bd9Sstevel@tonic-gate     return NULL;
3627c478bd9Sstevel@tonic-gate   };
3637c478bd9Sstevel@tonic-gate /*
3647c478bd9Sstevel@tonic-gate  * If the caller specified that the whole of path[] be matched,
3657c478bd9Sstevel@tonic-gate  * work out the corresponding length.
3667c478bd9Sstevel@tonic-gate  */
3677c478bd9Sstevel@tonic-gate   if(pathlen < 0 || pathlen > strlen(path))
3687c478bd9Sstevel@tonic-gate     pathlen = strlen(path);
3697c478bd9Sstevel@tonic-gate /*
3707c478bd9Sstevel@tonic-gate  * Discard previous expansion results.
3717c478bd9Sstevel@tonic-gate  */
3727c478bd9Sstevel@tonic-gate   ef_clear_files(ef);
3737c478bd9Sstevel@tonic-gate /*
3747c478bd9Sstevel@tonic-gate  * Preprocess the path, expanding ~/, ~user/ and $envvar references,
3757c478bd9Sstevel@tonic-gate  * using ef->path as a work directory and returning a pointer to
3767c478bd9Sstevel@tonic-gate  * a copy of the resulting pattern in the cache.
3777c478bd9Sstevel@tonic-gate  */
3787c478bd9Sstevel@tonic-gate   path = ef_expand_special(ef, path, pathlen);
3797c478bd9Sstevel@tonic-gate   if(!path)
3807c478bd9Sstevel@tonic-gate     return NULL;
3817c478bd9Sstevel@tonic-gate /*
3827c478bd9Sstevel@tonic-gate  * Clear the pathname buffer.
3837c478bd9Sstevel@tonic-gate  */
3847c478bd9Sstevel@tonic-gate   _pn_clear_path(ef->path);
3857c478bd9Sstevel@tonic-gate /*
3867c478bd9Sstevel@tonic-gate  * Does the pathname contain any wildcards?
3877c478bd9Sstevel@tonic-gate  */
3887c478bd9Sstevel@tonic-gate   for(wild=0,pptr=path; !wild && *pptr; pptr++) {
3897c478bd9Sstevel@tonic-gate     switch(*pptr) {
3907c478bd9Sstevel@tonic-gate     case '\\':                      /* Skip escaped characters */
3917c478bd9Sstevel@tonic-gate       if(pptr[1])
3927c478bd9Sstevel@tonic-gate 	pptr++;
393*55fea89dSDan Cross       break;
3947c478bd9Sstevel@tonic-gate     case '*': case '?': case '[':   /* A wildcard character? */
3957c478bd9Sstevel@tonic-gate       wild = 1;
3967c478bd9Sstevel@tonic-gate       break;
3977c478bd9Sstevel@tonic-gate     };
3987c478bd9Sstevel@tonic-gate   };
3997c478bd9Sstevel@tonic-gate /*
4007c478bd9Sstevel@tonic-gate  * If there are no wildcards to match, copy the current expanded
4017c478bd9Sstevel@tonic-gate  * path into the output array, removing backslash escapes while doing so.
4027c478bd9Sstevel@tonic-gate  */
4037c478bd9Sstevel@tonic-gate   if(!wild) {
4047c478bd9Sstevel@tonic-gate     if(ef_record_pathname(ef, path, 1))
4057c478bd9Sstevel@tonic-gate       return NULL;
4067c478bd9Sstevel@tonic-gate /*
4077c478bd9Sstevel@tonic-gate  * Does the filename exist?
4087c478bd9Sstevel@tonic-gate  */
4097c478bd9Sstevel@tonic-gate     ef->result.exists = _pu_file_exists(ef->result.files[0]);
4107c478bd9Sstevel@tonic-gate /*
4117c478bd9Sstevel@tonic-gate  * Match wildcards against existing files.
4127c478bd9Sstevel@tonic-gate  */
4137c478bd9Sstevel@tonic-gate   } else {
4147c478bd9Sstevel@tonic-gate /*
4157c478bd9Sstevel@tonic-gate  * Only existing files that match the pattern will be returned in the
4167c478bd9Sstevel@tonic-gate  * cache.
4177c478bd9Sstevel@tonic-gate  */
4187c478bd9Sstevel@tonic-gate     ef->result.exists = 1;
4197c478bd9Sstevel@tonic-gate /*
4207c478bd9Sstevel@tonic-gate  * Treat matching of the root-directory as a special case since it
4217c478bd9Sstevel@tonic-gate  * isn't contained in a directory.
4227c478bd9Sstevel@tonic-gate  */
4237c478bd9Sstevel@tonic-gate     if(strcmp(path, FS_ROOT_DIR) == 0) {
4247c478bd9Sstevel@tonic-gate       if(ef_record_pathname(ef, FS_ROOT_DIR, 0))
4257c478bd9Sstevel@tonic-gate 	return NULL;
4267c478bd9Sstevel@tonic-gate     } else {
4277c478bd9Sstevel@tonic-gate /*
4287c478bd9Sstevel@tonic-gate  * What should the top level directory of the search be?
4297c478bd9Sstevel@tonic-gate  */
4307c478bd9Sstevel@tonic-gate       if(strncmp(path, FS_ROOT_DIR, FS_ROOT_DIR_LEN) == 0) {
4317c478bd9Sstevel@tonic-gate 	dirname = FS_ROOT_DIR;
4327c478bd9Sstevel@tonic-gate 	if(!_pn_append_to_path(ef->path, FS_ROOT_DIR, -1, 0)) {
4337c478bd9Sstevel@tonic-gate 	  _err_record_msg(ef->err, "Insufficient memory to record path",
4347c478bd9Sstevel@tonic-gate 			  END_ERR_MSG);
4357c478bd9Sstevel@tonic-gate 	  return NULL;
4367c478bd9Sstevel@tonic-gate 	};
4377c478bd9Sstevel@tonic-gate 	path += FS_ROOT_DIR_LEN;
4387c478bd9Sstevel@tonic-gate       } else {
4397c478bd9Sstevel@tonic-gate 	dirname = FS_PWD;
4407c478bd9Sstevel@tonic-gate       };
4417c478bd9Sstevel@tonic-gate /*
4427c478bd9Sstevel@tonic-gate  * Open the top-level directory of the search.
4437c478bd9Sstevel@tonic-gate  */
4447c478bd9Sstevel@tonic-gate       dnode = ef_open_dir(ef, dirname);
4457c478bd9Sstevel@tonic-gate       if(!dnode)
4467c478bd9Sstevel@tonic-gate 	return NULL;
4477c478bd9Sstevel@tonic-gate /*
4487c478bd9Sstevel@tonic-gate  * Recursively match successive directory components of the path.
4497c478bd9Sstevel@tonic-gate  */
4507c478bd9Sstevel@tonic-gate       if(ef_match_relative_pathname(ef, dnode->dr, path, 0)) {
4517c478bd9Sstevel@tonic-gate 	dnode = ef_close_dir(ef, dnode);
4527c478bd9Sstevel@tonic-gate 	return NULL;
4537c478bd9Sstevel@tonic-gate       };
4547c478bd9Sstevel@tonic-gate /*
4557c478bd9Sstevel@tonic-gate  * Cleanup.
4567c478bd9Sstevel@tonic-gate  */
4577c478bd9Sstevel@tonic-gate       dnode = ef_close_dir(ef, dnode);
4587c478bd9Sstevel@tonic-gate     };
4597c478bd9Sstevel@tonic-gate /*
4607c478bd9Sstevel@tonic-gate  * No files matched?
4617c478bd9Sstevel@tonic-gate  */
4627c478bd9Sstevel@tonic-gate     if(ef->result.nfile < 1) {
4637c478bd9Sstevel@tonic-gate       _err_record_msg(ef->err, "No files match", END_ERR_MSG);
4647c478bd9Sstevel@tonic-gate       return NULL;
4657c478bd9Sstevel@tonic-gate     };
4667c478bd9Sstevel@tonic-gate /*
4677c478bd9Sstevel@tonic-gate  * Sort the pathnames that matched.
4687c478bd9Sstevel@tonic-gate  */
4697c478bd9Sstevel@tonic-gate     qsort(ef->result.files, ef->result.nfile, sizeof(ef->result.files[0]),
4707c478bd9Sstevel@tonic-gate 	  ef_cmp_strings);
4717c478bd9Sstevel@tonic-gate   };
4727c478bd9Sstevel@tonic-gate /*
4737c478bd9Sstevel@tonic-gate  * Return the result container.
4747c478bd9Sstevel@tonic-gate  */
4757c478bd9Sstevel@tonic-gate   return &ef->result;
4767c478bd9Sstevel@tonic-gate }
4777c478bd9Sstevel@tonic-gate 
4787c478bd9Sstevel@tonic-gate /*.......................................................................
4797c478bd9Sstevel@tonic-gate  * Attempt to recursively match the given pattern with the contents of
4807c478bd9Sstevel@tonic-gate  * the current directory, descending sub-directories as needed.
4817c478bd9Sstevel@tonic-gate  *
4827c478bd9Sstevel@tonic-gate  * Input:
4837c478bd9Sstevel@tonic-gate  *  ef      ExpandFile *  The pathname expansion resource object.
4847c478bd9Sstevel@tonic-gate  *  dr       DirReader *  The directory reader object of the directory
4857c478bd9Sstevel@tonic-gate  *                        to be searched.
4867c478bd9Sstevel@tonic-gate  *  pattern const char *  The pattern to match with files in the current
4877c478bd9Sstevel@tonic-gate  *                        directory.
4887c478bd9Sstevel@tonic-gate  *  separate       int    When appending a filename from the specified
4897c478bd9Sstevel@tonic-gate  *                        directory to ef->pathname, insert a directory
4907c478bd9Sstevel@tonic-gate  *                        separator between the existing pathname and
4917c478bd9Sstevel@tonic-gate  *                        the filename, unless separate is zero.
4927c478bd9Sstevel@tonic-gate  * Output:
4937c478bd9Sstevel@tonic-gate  *  return         int    0 - OK.
4947c478bd9Sstevel@tonic-gate  *                        1 - Error.
4957c478bd9Sstevel@tonic-gate  */
ef_match_relative_pathname(ExpandFile * ef,DirReader * dr,const char * pattern,int separate)4967c478bd9Sstevel@tonic-gate static int ef_match_relative_pathname(ExpandFile *ef, DirReader *dr,
4977c478bd9Sstevel@tonic-gate 				       const char *pattern, int separate)
4987c478bd9Sstevel@tonic-gate {
4997c478bd9Sstevel@tonic-gate   const char *nextp;  /* The pointer to the character that follows the part */
5007c478bd9Sstevel@tonic-gate                       /*  of the pattern that is to be matched with files */
5017c478bd9Sstevel@tonic-gate                       /*  in the current directory. */
5027c478bd9Sstevel@tonic-gate   char *file;         /* The name of the file being matched */
5037c478bd9Sstevel@tonic-gate   int pathlen;        /* The length of ef->pathname[] on entry to this */
5047c478bd9Sstevel@tonic-gate                       /*  function */
5057c478bd9Sstevel@tonic-gate /*
5067c478bd9Sstevel@tonic-gate  * Record the current length of the pathname string recorded in
5077c478bd9Sstevel@tonic-gate  * ef->pathname[].
5087c478bd9Sstevel@tonic-gate  */
5097c478bd9Sstevel@tonic-gate   pathlen = strlen(ef->path->name);
5107c478bd9Sstevel@tonic-gate /*
5117c478bd9Sstevel@tonic-gate  * Get a pointer to the character that follows the end of the part of
5127c478bd9Sstevel@tonic-gate  * the pattern that should be matched to files within the current directory.
5137c478bd9Sstevel@tonic-gate  * This will either point to a directory separator, or to the '\0' terminator
5147c478bd9Sstevel@tonic-gate  * of the pattern string.
5157c478bd9Sstevel@tonic-gate  */
5167c478bd9Sstevel@tonic-gate   for(nextp=pattern; *nextp && strncmp(nextp, FS_DIR_SEP, FS_DIR_SEP_LEN) != 0;
5177c478bd9Sstevel@tonic-gate       nextp++)
5187c478bd9Sstevel@tonic-gate     ;
5197c478bd9Sstevel@tonic-gate /*
5207c478bd9Sstevel@tonic-gate  * Read each file from the directory, attempting to match it to the
5217c478bd9Sstevel@tonic-gate  * current pattern.
5227c478bd9Sstevel@tonic-gate  */
5237c478bd9Sstevel@tonic-gate   while((file=_dr_next_file(dr)) != NULL) {
5247c478bd9Sstevel@tonic-gate /*
5257c478bd9Sstevel@tonic-gate  * Does the latest file match the pattern up to nextp?
5267c478bd9Sstevel@tonic-gate  */
5277c478bd9Sstevel@tonic-gate     if(ef_string_matches_pattern(file, pattern, file[0]=='.', nextp)) {
5287c478bd9Sstevel@tonic-gate /*
5297c478bd9Sstevel@tonic-gate  * Append the new directory entry to the current matching pathname.
5307c478bd9Sstevel@tonic-gate  */
5317c478bd9Sstevel@tonic-gate       if((separate && _pn_append_to_path(ef->path, FS_DIR_SEP, -1, 0)==NULL) ||
5327c478bd9Sstevel@tonic-gate 	 _pn_append_to_path(ef->path, file, -1, 0)==NULL) {
5337c478bd9Sstevel@tonic-gate 	_err_record_msg(ef->err, "Insufficient memory to record path",
5347c478bd9Sstevel@tonic-gate 			END_ERR_MSG);
5357c478bd9Sstevel@tonic-gate 	return 1;
5367c478bd9Sstevel@tonic-gate       };
5377c478bd9Sstevel@tonic-gate /*
5387c478bd9Sstevel@tonic-gate  * If we have reached the end of the pattern, record the accumulated
5397c478bd9Sstevel@tonic-gate  * pathname in the list of matching files.
5407c478bd9Sstevel@tonic-gate  */
5417c478bd9Sstevel@tonic-gate       if(*nextp == '\0') {
5427c478bd9Sstevel@tonic-gate 	if(ef_record_pathname(ef, ef->path->name, 0))
5437c478bd9Sstevel@tonic-gate 	  return 1;
5447c478bd9Sstevel@tonic-gate /*
5457c478bd9Sstevel@tonic-gate  * If the matching directory entry is a subdirectory, and the
5467c478bd9Sstevel@tonic-gate  * next character of the pattern is a directory separator,
5477c478bd9Sstevel@tonic-gate  * recursively call the current function to scan the sub-directory
5487c478bd9Sstevel@tonic-gate  * for matches.
5497c478bd9Sstevel@tonic-gate  */
5507c478bd9Sstevel@tonic-gate       } else if(_pu_path_is_dir(ef->path->name) &&
5517c478bd9Sstevel@tonic-gate 		strncmp(nextp, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0) {
5527c478bd9Sstevel@tonic-gate /*
5537c478bd9Sstevel@tonic-gate  * If the pattern finishes with the directory separator, then
5547c478bd9Sstevel@tonic-gate  * record the pathame as matching.
5557c478bd9Sstevel@tonic-gate  */
5567c478bd9Sstevel@tonic-gate 	if(nextp[FS_DIR_SEP_LEN] == '\0') {
5577c478bd9Sstevel@tonic-gate 	  if(ef_record_pathname(ef, ef->path->name, 0))
5587c478bd9Sstevel@tonic-gate 	    return 1;
5597c478bd9Sstevel@tonic-gate /*
5607c478bd9Sstevel@tonic-gate  * Match files within the directory.
5617c478bd9Sstevel@tonic-gate  */
5627c478bd9Sstevel@tonic-gate 	} else {
5637c478bd9Sstevel@tonic-gate 	  DirNode *subdnode = ef_open_dir(ef, ef->path->name);
5647c478bd9Sstevel@tonic-gate 	  if(subdnode) {
5657c478bd9Sstevel@tonic-gate 	    if(ef_match_relative_pathname(ef, subdnode->dr,
5667c478bd9Sstevel@tonic-gate 					   nextp+FS_DIR_SEP_LEN, 1)) {
5677c478bd9Sstevel@tonic-gate 	      subdnode = ef_close_dir(ef, subdnode);
5687c478bd9Sstevel@tonic-gate 	      return 1;
5697c478bd9Sstevel@tonic-gate 	    };
5707c478bd9Sstevel@tonic-gate 	    subdnode = ef_close_dir(ef, subdnode);
5717c478bd9Sstevel@tonic-gate 	  };
5727c478bd9Sstevel@tonic-gate 	};
5737c478bd9Sstevel@tonic-gate       };
5747c478bd9Sstevel@tonic-gate /*
5757c478bd9Sstevel@tonic-gate  * Remove the latest filename from the pathname string, so that
5767c478bd9Sstevel@tonic-gate  * another matching file can be appended.
5777c478bd9Sstevel@tonic-gate  */
5787c478bd9Sstevel@tonic-gate       ef->path->name[pathlen] = '\0';
5797c478bd9Sstevel@tonic-gate     };
5807c478bd9Sstevel@tonic-gate   };
5817c478bd9Sstevel@tonic-gate   return 0;
5827c478bd9Sstevel@tonic-gate }
5837c478bd9Sstevel@tonic-gate 
5847c478bd9Sstevel@tonic-gate /*.......................................................................
5857c478bd9Sstevel@tonic-gate  * Record a new matching filename.
5867c478bd9Sstevel@tonic-gate  *
5877c478bd9Sstevel@tonic-gate  * Input:
5887c478bd9Sstevel@tonic-gate  *  ef        ExpandFile *  The filename-match resource object.
5897c478bd9Sstevel@tonic-gate  *  pathname  const char *  The pathname to record.
5907c478bd9Sstevel@tonic-gate  *  remove_escapes   int    If true, remove backslash escapes in the
5917c478bd9Sstevel@tonic-gate  *                          recorded copy of the pathname.
5927c478bd9Sstevel@tonic-gate  * Output:
5937c478bd9Sstevel@tonic-gate  *  return           int     0 - OK.
5947c478bd9Sstevel@tonic-gate  *                           1 - Error (ef->err will contain a
5957c478bd9Sstevel@tonic-gate  *                                      description of the error).
5967c478bd9Sstevel@tonic-gate  */
ef_record_pathname(ExpandFile * ef,const char * pathname,int remove_escapes)5977c478bd9Sstevel@tonic-gate static int ef_record_pathname(ExpandFile *ef, const char *pathname,
5987c478bd9Sstevel@tonic-gate 			      int remove_escapes)
5997c478bd9Sstevel@tonic-gate {
6007c478bd9Sstevel@tonic-gate   char *copy;          /* The recorded copy of pathname[] */
6017c478bd9Sstevel@tonic-gate /*
6027c478bd9Sstevel@tonic-gate  * Attempt to make a copy of the pathname in the cache.
6037c478bd9Sstevel@tonic-gate  */
6047c478bd9Sstevel@tonic-gate   copy = ef_cache_pathname(ef, pathname, remove_escapes);
6057c478bd9Sstevel@tonic-gate   if(!copy)
6067c478bd9Sstevel@tonic-gate     return 1;
6077c478bd9Sstevel@tonic-gate /*
6087c478bd9Sstevel@tonic-gate  * If there isn't room to record a pointer to the recorded pathname in the
6097c478bd9Sstevel@tonic-gate  * array of files, attempt to extend the array.
6107c478bd9Sstevel@tonic-gate  */
6117c478bd9Sstevel@tonic-gate   if(ef->result.nfile + 1 > ef->files_dim) {
6127c478bd9Sstevel@tonic-gate     int files_dim = ef->files_dim + MATCH_BLK_FACT;
6137c478bd9Sstevel@tonic-gate     char **files = (char **) realloc(ef->result.files,
6147c478bd9Sstevel@tonic-gate 				     files_dim * sizeof(files[0]));
6157c478bd9Sstevel@tonic-gate     if(!files) {
6167c478bd9Sstevel@tonic-gate       _err_record_msg(ef->err,
6177c478bd9Sstevel@tonic-gate 	     "Insufficient memory to record all of the matching filenames",
6187c478bd9Sstevel@tonic-gate 	     END_ERR_MSG);
6197c478bd9Sstevel@tonic-gate       errno = ENOMEM;
6207c478bd9Sstevel@tonic-gate       return 1;
6217c478bd9Sstevel@tonic-gate     };
6227c478bd9Sstevel@tonic-gate     ef->result.files = files;
6237c478bd9Sstevel@tonic-gate     ef->files_dim = files_dim;
6247c478bd9Sstevel@tonic-gate   };
6257c478bd9Sstevel@tonic-gate /*
6267c478bd9Sstevel@tonic-gate  * Record a pointer to the new match.
6277c478bd9Sstevel@tonic-gate  */
6287c478bd9Sstevel@tonic-gate   ef->result.files[ef->result.nfile++] = copy;
6297c478bd9Sstevel@tonic-gate   return 0;
6307c478bd9Sstevel@tonic-gate }
6317c478bd9Sstevel@tonic-gate 
6327c478bd9Sstevel@tonic-gate /*.......................................................................
6337c478bd9Sstevel@tonic-gate  * Record a pathname in the cache.
6347c478bd9Sstevel@tonic-gate  *
6357c478bd9Sstevel@tonic-gate  * Input:
6367c478bd9Sstevel@tonic-gate  *  ef       ExpandFile *  The filename-match resource object.
6377c478bd9Sstevel@tonic-gate  *  pathname       char *  The pathname to record.
6387c478bd9Sstevel@tonic-gate  *  remove_escapes  int    If true, remove backslash escapes in the
6397c478bd9Sstevel@tonic-gate  *                         copy of the pathname.
6407c478bd9Sstevel@tonic-gate  * Output:
6417c478bd9Sstevel@tonic-gate  *  return         char *  The pointer to the copy of the pathname.
6427c478bd9Sstevel@tonic-gate  *                         On error NULL is returned and a description
6437c478bd9Sstevel@tonic-gate  *                         of the error is left in ef->err.
6447c478bd9Sstevel@tonic-gate  */
ef_cache_pathname(ExpandFile * ef,const char * pathname,int remove_escapes)6457c478bd9Sstevel@tonic-gate static char *ef_cache_pathname(ExpandFile *ef, const char *pathname,
6467c478bd9Sstevel@tonic-gate 			       int remove_escapes)
6477c478bd9Sstevel@tonic-gate {
6487c478bd9Sstevel@tonic-gate   char *copy = _sg_store_string(ef->sg, pathname, remove_escapes);
6497c478bd9Sstevel@tonic-gate   if(!copy)
6507c478bd9Sstevel@tonic-gate     _err_record_msg(ef->err, "Insufficient memory to store pathname",
6517c478bd9Sstevel@tonic-gate 		    END_ERR_MSG);
6527c478bd9Sstevel@tonic-gate   return copy;
6537c478bd9Sstevel@tonic-gate }
6547c478bd9Sstevel@tonic-gate 
6557c478bd9Sstevel@tonic-gate /*.......................................................................
6567c478bd9Sstevel@tonic-gate  * Clear the results of the previous expansion operation, ready for the
6577c478bd9Sstevel@tonic-gate  * next.
6587c478bd9Sstevel@tonic-gate  *
6597c478bd9Sstevel@tonic-gate  * Input:
6607c478bd9Sstevel@tonic-gate  *  ef    ExpandFile *  The pathname expansion resource object.
6617c478bd9Sstevel@tonic-gate  */
ef_clear_files(ExpandFile * ef)6627c478bd9Sstevel@tonic-gate static void ef_clear_files(ExpandFile *ef)
6637c478bd9Sstevel@tonic-gate {
6647c478bd9Sstevel@tonic-gate   _clr_StringGroup(ef->sg);
6657c478bd9Sstevel@tonic-gate   _pn_clear_path(ef->path);
6667c478bd9Sstevel@tonic-gate   ef->result.exists = 0;
6677c478bd9Sstevel@tonic-gate   ef->result.nfile = 0;
6687c478bd9Sstevel@tonic-gate   _err_clear_msg(ef->err);
6697c478bd9Sstevel@tonic-gate   return;
6707c478bd9Sstevel@tonic-gate }
6717c478bd9Sstevel@tonic-gate 
6727c478bd9Sstevel@tonic-gate /*.......................................................................
6737c478bd9Sstevel@tonic-gate  * Get a new directory reader object from the cache.
6747c478bd9Sstevel@tonic-gate  *
6757c478bd9Sstevel@tonic-gate  * Input:
6767c478bd9Sstevel@tonic-gate  *  ef        ExpandFile *  The pathname expansion resource object.
6777c478bd9Sstevel@tonic-gate  *  pathname  const char *  The pathname of the directory.
6787c478bd9Sstevel@tonic-gate  * Output:
6797c478bd9Sstevel@tonic-gate  *  return       DirNode *  The cache entry of the new directory reader,
6807c478bd9Sstevel@tonic-gate  *                          or NULL on error. On error, ef->err will
6817c478bd9Sstevel@tonic-gate  *                          contain a description of the error.
6827c478bd9Sstevel@tonic-gate  */
ef_open_dir(ExpandFile * ef,const char * pathname)6837c478bd9Sstevel@tonic-gate static DirNode *ef_open_dir(ExpandFile *ef, const char *pathname)
6847c478bd9Sstevel@tonic-gate {
6857c478bd9Sstevel@tonic-gate   char *errmsg = NULL;  /* An error message from a called function */
6867c478bd9Sstevel@tonic-gate   DirNode *node;        /* The cache node used */
6877c478bd9Sstevel@tonic-gate /*
6887c478bd9Sstevel@tonic-gate  * Get the directory reader cache.
6897c478bd9Sstevel@tonic-gate  */
6907c478bd9Sstevel@tonic-gate   DirCache *cache = &ef->cache;
6917c478bd9Sstevel@tonic-gate /*
6927c478bd9Sstevel@tonic-gate  * Extend the cache if there are no free cache nodes.
6937c478bd9Sstevel@tonic-gate  */
6947c478bd9Sstevel@tonic-gate   if(!cache->next) {
6957c478bd9Sstevel@tonic-gate     node = (DirNode *) _new_FreeListNode(cache->mem);
6967c478bd9Sstevel@tonic-gate     if(!node) {
6977c478bd9Sstevel@tonic-gate       _err_record_msg(ef->err, "Insufficient memory to open a new directory",
6987c478bd9Sstevel@tonic-gate 		      END_ERR_MSG);
6997c478bd9Sstevel@tonic-gate       return NULL;
7007c478bd9Sstevel@tonic-gate     };
7017c478bd9Sstevel@tonic-gate /*
7027c478bd9Sstevel@tonic-gate  * Initialize the cache node.
7037c478bd9Sstevel@tonic-gate  */
7047c478bd9Sstevel@tonic-gate     node->next = NULL;
7057c478bd9Sstevel@tonic-gate     node->prev = NULL;
7067c478bd9Sstevel@tonic-gate     node->dr = NULL;
7077c478bd9Sstevel@tonic-gate /*
7087c478bd9Sstevel@tonic-gate  * Allocate a directory reader object.
7097c478bd9Sstevel@tonic-gate  */
7107c478bd9Sstevel@tonic-gate     node->dr = _new_DirReader();
7117c478bd9Sstevel@tonic-gate     if(!node->dr) {
7127c478bd9Sstevel@tonic-gate       _err_record_msg(ef->err, "Insufficient memory to open a new directory",
7137c478bd9Sstevel@tonic-gate 		      END_ERR_MSG);
7147c478bd9Sstevel@tonic-gate       node = (DirNode *) _del_FreeListNode(cache->mem, node);
7157c478bd9Sstevel@tonic-gate       return NULL;
7167c478bd9Sstevel@tonic-gate     };
7177c478bd9Sstevel@tonic-gate /*
7187c478bd9Sstevel@tonic-gate  * Append the node to the cache list.
7197c478bd9Sstevel@tonic-gate  */
7207c478bd9Sstevel@tonic-gate     node->prev = cache->tail;
7217c478bd9Sstevel@tonic-gate     if(cache->tail)
7227c478bd9Sstevel@tonic-gate       cache->tail->next = node;
7237c478bd9Sstevel@tonic-gate     else
7247c478bd9Sstevel@tonic-gate       cache->head = node;
7257c478bd9Sstevel@tonic-gate     cache->next = cache->tail = node;
7267c478bd9Sstevel@tonic-gate   };
7277c478bd9Sstevel@tonic-gate /*
7287c478bd9Sstevel@tonic-gate  * Get the first unused node, but don't remove it from the list yet.
7297c478bd9Sstevel@tonic-gate  */
7307c478bd9Sstevel@tonic-gate   node = cache->next;
7317c478bd9Sstevel@tonic-gate /*
7327c478bd9Sstevel@tonic-gate  * Attempt to open the specified directory.
7337c478bd9Sstevel@tonic-gate  */
7347c478bd9Sstevel@tonic-gate   if(_dr_open_dir(node->dr, pathname, &errmsg)) {
7357c478bd9Sstevel@tonic-gate     _err_record_msg(ef->err, errmsg, END_ERR_MSG);
7367c478bd9Sstevel@tonic-gate     return NULL;
7377c478bd9Sstevel@tonic-gate   };
7387c478bd9Sstevel@tonic-gate /*
7397c478bd9Sstevel@tonic-gate  * Now that we have successfully opened the specified directory,
7407c478bd9Sstevel@tonic-gate  * remove the cache node from the list, and relink the list around it.
7417c478bd9Sstevel@tonic-gate  */
7427c478bd9Sstevel@tonic-gate   cache->next = node->next;
7437c478bd9Sstevel@tonic-gate   if(node->prev)
7447c478bd9Sstevel@tonic-gate     node->prev->next = node->next;
7457c478bd9Sstevel@tonic-gate   else
7467c478bd9Sstevel@tonic-gate     cache->head = node->next;
7477c478bd9Sstevel@tonic-gate   if(node->next)
7487c478bd9Sstevel@tonic-gate     node->next->prev = node->prev;
7497c478bd9Sstevel@tonic-gate   else
7507c478bd9Sstevel@tonic-gate     cache->tail = node->prev;
7517c478bd9Sstevel@tonic-gate   node->next = node->prev = NULL;
7527c478bd9Sstevel@tonic-gate /*
7537c478bd9Sstevel@tonic-gate  * Return the successfully initialized cache node to the caller.
7547c478bd9Sstevel@tonic-gate  */
7557c478bd9Sstevel@tonic-gate   return node;
7567c478bd9Sstevel@tonic-gate }
7577c478bd9Sstevel@tonic-gate 
7587c478bd9Sstevel@tonic-gate /*.......................................................................
7597c478bd9Sstevel@tonic-gate  * Return a directory reader object to the cache, after first closing
7607c478bd9Sstevel@tonic-gate  * the directory that it was managing.
7617c478bd9Sstevel@tonic-gate  *
7627c478bd9Sstevel@tonic-gate  * Input:
7637c478bd9Sstevel@tonic-gate  *  ef    ExpandFile *  The pathname expansion resource object.
7647c478bd9Sstevel@tonic-gate  *  node     DirNode *  The cache entry of the directory reader, as returned
7657c478bd9Sstevel@tonic-gate  *                      by ef_open_dir().
7667c478bd9Sstevel@tonic-gate  * Output:
7677c478bd9Sstevel@tonic-gate  *  return   DirNode *  The deleted DirNode (ie. allways NULL).
7687c478bd9Sstevel@tonic-gate  */
ef_close_dir(ExpandFile * ef,DirNode * node)7697c478bd9Sstevel@tonic-gate static DirNode *ef_close_dir(ExpandFile *ef, DirNode *node)
7707c478bd9Sstevel@tonic-gate {
7717c478bd9Sstevel@tonic-gate /*
7727c478bd9Sstevel@tonic-gate  * Get the directory reader cache.
7737c478bd9Sstevel@tonic-gate  */
7747c478bd9Sstevel@tonic-gate   DirCache *cache = &ef->cache;
7757c478bd9Sstevel@tonic-gate /*
7767c478bd9Sstevel@tonic-gate  * Close the directory.
7777c478bd9Sstevel@tonic-gate  */
7787c478bd9Sstevel@tonic-gate   _dr_close_dir(node->dr);
7797c478bd9Sstevel@tonic-gate /*
7807c478bd9Sstevel@tonic-gate  * Return the node to the tail of the cache list.
7817c478bd9Sstevel@tonic-gate  */
7827c478bd9Sstevel@tonic-gate   node->next = NULL;
7837c478bd9Sstevel@tonic-gate   node->prev = cache->tail;
7847c478bd9Sstevel@tonic-gate   if(cache->tail)
7857c478bd9Sstevel@tonic-gate     cache->tail->next = node;
7867c478bd9Sstevel@tonic-gate   else
7877c478bd9Sstevel@tonic-gate     cache->head = cache->tail = node;
7887c478bd9Sstevel@tonic-gate   if(!cache->next)
7897c478bd9Sstevel@tonic-gate     cache->next = node;
7907c478bd9Sstevel@tonic-gate   return NULL;
7917c478bd9Sstevel@tonic-gate }
7927c478bd9Sstevel@tonic-gate 
7937c478bd9Sstevel@tonic-gate /*.......................................................................
7947c478bd9Sstevel@tonic-gate  * Return non-zero if the specified file name matches a given glob
7957c478bd9Sstevel@tonic-gate  * pattern.
7967c478bd9Sstevel@tonic-gate  *
7977c478bd9Sstevel@tonic-gate  * Input:
7987c478bd9Sstevel@tonic-gate  *  file     const char *  The file-name component to be matched to the pattern.
7997c478bd9Sstevel@tonic-gate  *  pattern  const char *  The start of the pattern to match against file[].
8007c478bd9Sstevel@tonic-gate  *  xplicit         int    If non-zero, the first character must be matched
8017c478bd9Sstevel@tonic-gate  *                         explicitly (ie. not with a wildcard).
8027c478bd9Sstevel@tonic-gate  *  nextp    const char *  The pointer to the the character following the
8037c478bd9Sstevel@tonic-gate  *                         end of the pattern in pattern[].
8047c478bd9Sstevel@tonic-gate  * Output:
8057c478bd9Sstevel@tonic-gate  *  return    int          0 - Doesn't match.
8067c478bd9Sstevel@tonic-gate  *                         1 - The file-name string matches the pattern.
8077c478bd9Sstevel@tonic-gate  */
ef_string_matches_pattern(const char * file,const char * pattern,int xplicit,const char * nextp)8087c478bd9Sstevel@tonic-gate static int ef_string_matches_pattern(const char *file, const char *pattern,
8097c478bd9Sstevel@tonic-gate 				      int xplicit, const char *nextp)
8107c478bd9Sstevel@tonic-gate {
8117c478bd9Sstevel@tonic-gate   const char *pptr = pattern; /* The pointer used to scan the pattern */
8127c478bd9Sstevel@tonic-gate   const char *fptr = file;    /* The pointer used to scan the filename string */
8137c478bd9Sstevel@tonic-gate /*
8147c478bd9Sstevel@tonic-gate  * Match each character of the pattern in turn.
8157c478bd9Sstevel@tonic-gate  */
8167c478bd9Sstevel@tonic-gate   while(pptr < nextp) {
8177c478bd9Sstevel@tonic-gate /*
8187c478bd9Sstevel@tonic-gate  * Handle the next character of the pattern.
8197c478bd9Sstevel@tonic-gate  */
8207c478bd9Sstevel@tonic-gate     switch(*pptr) {
8217c478bd9Sstevel@tonic-gate /*
8227c478bd9Sstevel@tonic-gate  * A match zero-or-more characters wildcard operator.
8237c478bd9Sstevel@tonic-gate  */
8247c478bd9Sstevel@tonic-gate     case '*':
8257c478bd9Sstevel@tonic-gate /*
8267c478bd9Sstevel@tonic-gate  * Skip the '*' character in the pattern.
8277c478bd9Sstevel@tonic-gate  */
8287c478bd9Sstevel@tonic-gate       pptr++;
8297c478bd9Sstevel@tonic-gate /*
8307c478bd9Sstevel@tonic-gate  * If wildcards aren't allowed, the pattern doesn't match.
8317c478bd9Sstevel@tonic-gate  */
8327c478bd9Sstevel@tonic-gate       if(xplicit)
8337c478bd9Sstevel@tonic-gate 	return 0;
8347c478bd9Sstevel@tonic-gate /*
8357c478bd9Sstevel@tonic-gate  * If the pattern ends with a the '*' wildcard, then the
8367c478bd9Sstevel@tonic-gate  * rest of the filename matches this.
8377c478bd9Sstevel@tonic-gate  */
8387c478bd9Sstevel@tonic-gate       if(pptr >= nextp)
8397c478bd9Sstevel@tonic-gate 	return 1;
8407c478bd9Sstevel@tonic-gate /*
8417c478bd9Sstevel@tonic-gate  * Using the wildcard to match successively longer sections of
8427c478bd9Sstevel@tonic-gate  * the remaining characters of the filename, attempt to match
8437c478bd9Sstevel@tonic-gate  * the tail of the filename against the tail of the pattern.
8447c478bd9Sstevel@tonic-gate  */
8457c478bd9Sstevel@tonic-gate       for( ; *fptr; fptr++) {
8467c478bd9Sstevel@tonic-gate 	if(ef_string_matches_pattern(fptr, pptr, 0, nextp))
8477c478bd9Sstevel@tonic-gate 	  return 1;
8487c478bd9Sstevel@tonic-gate       };
8497c478bd9Sstevel@tonic-gate       return 0; /* The pattern following the '*' didn't match */
8507c478bd9Sstevel@tonic-gate       break;
8517c478bd9Sstevel@tonic-gate /*
8527c478bd9Sstevel@tonic-gate  * A match-one-character wildcard operator.
8537c478bd9Sstevel@tonic-gate  */
8547c478bd9Sstevel@tonic-gate     case '?':
8557c478bd9Sstevel@tonic-gate /*
8567c478bd9Sstevel@tonic-gate  * If there is a character to be matched, skip it and advance the
8577c478bd9Sstevel@tonic-gate  * pattern pointer.
8587c478bd9Sstevel@tonic-gate  */
8597c478bd9Sstevel@tonic-gate       if(!xplicit && *fptr) {
8607c478bd9Sstevel@tonic-gate         fptr++;
8617c478bd9Sstevel@tonic-gate         pptr++;
8627c478bd9Sstevel@tonic-gate /*
8637c478bd9Sstevel@tonic-gate  * If we hit the end of the filename string, there is no character
8647c478bd9Sstevel@tonic-gate  * matching the operator, so the string doesn't match.
8657c478bd9Sstevel@tonic-gate  */
8667c478bd9Sstevel@tonic-gate       } else {
8677c478bd9Sstevel@tonic-gate         return 0;
8687c478bd9Sstevel@tonic-gate       };
8697c478bd9Sstevel@tonic-gate       break;
8707c478bd9Sstevel@tonic-gate /*
8717c478bd9Sstevel@tonic-gate  * A character range operator, with the character ranges enclosed
8727c478bd9Sstevel@tonic-gate  * in matching square brackets.
8737c478bd9Sstevel@tonic-gate  */
8747c478bd9Sstevel@tonic-gate     case '[':
8757c478bd9Sstevel@tonic-gate       if(xplicit || !ef_matches_range(*fptr++, ++pptr, &pptr))
8767c478bd9Sstevel@tonic-gate         return 0;
8777c478bd9Sstevel@tonic-gate       break;
8787c478bd9Sstevel@tonic-gate /*
8797c478bd9Sstevel@tonic-gate  * A backslash in the pattern prevents the following character as
8807c478bd9Sstevel@tonic-gate  * being seen as a special character.
8817c478bd9Sstevel@tonic-gate  */
8827c478bd9Sstevel@tonic-gate     case '\\':
8837c478bd9Sstevel@tonic-gate       pptr++;
8847c478bd9Sstevel@tonic-gate       /* Note fallthrough to default */
8857c478bd9Sstevel@tonic-gate /*
8867c478bd9Sstevel@tonic-gate  * A normal character to be matched explicitly.
8877c478bd9Sstevel@tonic-gate  */
888cf421650SToomas Soome       /* FALLTHROUGH */
8897c478bd9Sstevel@tonic-gate     default:
8907c478bd9Sstevel@tonic-gate       if(*fptr == *pptr) {
8917c478bd9Sstevel@tonic-gate         fptr++;
8927c478bd9Sstevel@tonic-gate         pptr++;
8937c478bd9Sstevel@tonic-gate       } else {
8947c478bd9Sstevel@tonic-gate         return 0;
8957c478bd9Sstevel@tonic-gate       };
8967c478bd9Sstevel@tonic-gate       break;
8977c478bd9Sstevel@tonic-gate     };
8987c478bd9Sstevel@tonic-gate /*
8997c478bd9Sstevel@tonic-gate  * After passing the first character, turn off the explicit match
9007c478bd9Sstevel@tonic-gate  * requirement.
9017c478bd9Sstevel@tonic-gate  */
9027c478bd9Sstevel@tonic-gate     xplicit = 0;
9037c478bd9Sstevel@tonic-gate   };
9047c478bd9Sstevel@tonic-gate /*
9057c478bd9Sstevel@tonic-gate  * To get here the pattern must have been exhausted. If the filename
9067c478bd9Sstevel@tonic-gate  * string matched, then the filename string must also have been
9077c478bd9Sstevel@tonic-gate  * exhausted.
9087c478bd9Sstevel@tonic-gate  */
9097c478bd9Sstevel@tonic-gate   return *fptr == '\0';
9107c478bd9Sstevel@tonic-gate }
9117c478bd9Sstevel@tonic-gate 
9127c478bd9Sstevel@tonic-gate /*.......................................................................
9137c478bd9Sstevel@tonic-gate  * Match a character range expression terminated by an unescaped close
9147c478bd9Sstevel@tonic-gate  * square bracket.
9157c478bd9Sstevel@tonic-gate  *
9167c478bd9Sstevel@tonic-gate  * Input:
9177c478bd9Sstevel@tonic-gate  *  c               int     The character to be matched with the range
9187c478bd9Sstevel@tonic-gate  *                          pattern.
9197c478bd9Sstevel@tonic-gate  *  pattern  const char *   The range pattern to be matched (ie. after the
9207c478bd9Sstevel@tonic-gate  *                          initiating '[' character).
9217c478bd9Sstevel@tonic-gate  *  endp     const char **  On output a pointer to the character following the
9227c478bd9Sstevel@tonic-gate  *                          range expression will be assigned to *endp.
9237c478bd9Sstevel@tonic-gate  * Output:
9247c478bd9Sstevel@tonic-gate  *  return          int     0 - Doesn't match.
9257c478bd9Sstevel@tonic-gate  *                          1 - The character matched.
9267c478bd9Sstevel@tonic-gate  */
ef_matches_range(int c,const char * pattern,const char ** endp)9277c478bd9Sstevel@tonic-gate static int ef_matches_range(int c, const char *pattern, const char **endp)
9287c478bd9Sstevel@tonic-gate {
9297c478bd9Sstevel@tonic-gate   const char *pptr = pattern;  /* The pointer used to scan the pattern */
9307c478bd9Sstevel@tonic-gate   int invert = 0;              /* True to invert the sense of the match */
9317c478bd9Sstevel@tonic-gate   int matched = 0;             /* True if the character matched the pattern */
9327c478bd9Sstevel@tonic-gate /*
9337c478bd9Sstevel@tonic-gate  * If the first character is a caret, the sense of the match is
9347c478bd9Sstevel@tonic-gate  * inverted and only if the character isn't one of those in the
9357c478bd9Sstevel@tonic-gate  * range, do we say that it matches.
9367c478bd9Sstevel@tonic-gate  */
9377c478bd9Sstevel@tonic-gate   if(*pptr == '^') {
9387c478bd9Sstevel@tonic-gate     pptr++;
9397c478bd9Sstevel@tonic-gate     invert = 1;
9407c478bd9Sstevel@tonic-gate   };
9417c478bd9Sstevel@tonic-gate /*
9427c478bd9Sstevel@tonic-gate  * The hyphen is only a special character when it follows the first
9437c478bd9Sstevel@tonic-gate  * character of the range (not including the caret).
9447c478bd9Sstevel@tonic-gate  */
9457c478bd9Sstevel@tonic-gate   if(*pptr == '-') {
9467c478bd9Sstevel@tonic-gate     pptr++;
9477c478bd9Sstevel@tonic-gate     if(c == '-') {
9487c478bd9Sstevel@tonic-gate       *endp = pptr;
9497c478bd9Sstevel@tonic-gate       matched = 1;
9507c478bd9Sstevel@tonic-gate     };
9517c478bd9Sstevel@tonic-gate /*
9527c478bd9Sstevel@tonic-gate  * Skip other leading '-' characters since they make no sense.
9537c478bd9Sstevel@tonic-gate  */
9547c478bd9Sstevel@tonic-gate     while(*pptr == '-')
9557c478bd9Sstevel@tonic-gate       pptr++;
9567c478bd9Sstevel@tonic-gate   };
9577c478bd9Sstevel@tonic-gate /*
9587c478bd9Sstevel@tonic-gate  * The hyphen is only a special character when it follows the first
9597c478bd9Sstevel@tonic-gate  * character of the range (not including the caret or a hyphen).
9607c478bd9Sstevel@tonic-gate  */
9617c478bd9Sstevel@tonic-gate   if(*pptr == ']') {
9627c478bd9Sstevel@tonic-gate     pptr++;
9637c478bd9Sstevel@tonic-gate     if(c == ']') {
9647c478bd9Sstevel@tonic-gate       *endp = pptr;
9657c478bd9Sstevel@tonic-gate       matched = 1;
9667c478bd9Sstevel@tonic-gate     };
9677c478bd9Sstevel@tonic-gate   };
9687c478bd9Sstevel@tonic-gate /*
9697c478bd9Sstevel@tonic-gate  * Having dealt with the characters that have special meanings at
9707c478bd9Sstevel@tonic-gate  * the beginning of a character range expression, see if the
9717c478bd9Sstevel@tonic-gate  * character matches any of the remaining characters of the range,
9727c478bd9Sstevel@tonic-gate  * up until a terminating ']' character is seen.
9737c478bd9Sstevel@tonic-gate  */
9747c478bd9Sstevel@tonic-gate   while(!matched && *pptr && *pptr != ']') {
9757c478bd9Sstevel@tonic-gate /*
9767c478bd9Sstevel@tonic-gate  * Is this a range of characters signaled by the two end characters
9777c478bd9Sstevel@tonic-gate  * separated by a hyphen?
9787c478bd9Sstevel@tonic-gate  */
9797c478bd9Sstevel@tonic-gate     if(*pptr == '-') {
9807c478bd9Sstevel@tonic-gate       if(pptr[1] != ']') {
9817c478bd9Sstevel@tonic-gate         if(c >= pptr[-1] && c <= pptr[1])
9827c478bd9Sstevel@tonic-gate 	  matched = 1;
9837c478bd9Sstevel@tonic-gate 	pptr += 2;
9847c478bd9Sstevel@tonic-gate       };
9857c478bd9Sstevel@tonic-gate /*
9867c478bd9Sstevel@tonic-gate  * A normal character to be compared directly.
9877c478bd9Sstevel@tonic-gate  */
9887c478bd9Sstevel@tonic-gate     } else if(*pptr++ == c) {
9897c478bd9Sstevel@tonic-gate       matched = 1;
9907c478bd9Sstevel@tonic-gate     };
9917c478bd9Sstevel@tonic-gate   };
9927c478bd9Sstevel@tonic-gate /*
9937c478bd9Sstevel@tonic-gate  * Find the terminating ']'.
9947c478bd9Sstevel@tonic-gate  */
9957c478bd9Sstevel@tonic-gate   while(*pptr && *pptr != ']')
9967c478bd9Sstevel@tonic-gate     pptr++;
9977c478bd9Sstevel@tonic-gate /*
9987c478bd9Sstevel@tonic-gate  * Did we find a terminating ']'?
9997c478bd9Sstevel@tonic-gate  */
10007c478bd9Sstevel@tonic-gate   if(*pptr == ']') {
10017c478bd9Sstevel@tonic-gate     *endp = pptr + 1;
10027c478bd9Sstevel@tonic-gate     return matched ? !invert : invert;
10037c478bd9Sstevel@tonic-gate   };
10047c478bd9Sstevel@tonic-gate /*
10057c478bd9Sstevel@tonic-gate  * If the pattern didn't end with a ']' then it doesn't match, regardless
10067c478bd9Sstevel@tonic-gate  * of the value of the required sense of the match.
10077c478bd9Sstevel@tonic-gate  */
10087c478bd9Sstevel@tonic-gate   *endp = pptr;
10097c478bd9Sstevel@tonic-gate   return 0;
10107c478bd9Sstevel@tonic-gate }
10117c478bd9Sstevel@tonic-gate 
10127c478bd9Sstevel@tonic-gate /*.......................................................................
10137c478bd9Sstevel@tonic-gate  * This is a qsort() comparison function used to sort strings.
10147c478bd9Sstevel@tonic-gate  *
10157c478bd9Sstevel@tonic-gate  * Input:
10167c478bd9Sstevel@tonic-gate  *  v1, v2   void *  Pointers to the two strings to be compared.
10177c478bd9Sstevel@tonic-gate  * Output:
10187c478bd9Sstevel@tonic-gate  *  return    int    -1 -> v1 < v2.
10197c478bd9Sstevel@tonic-gate  *                    0 -> v1 == v2
10207c478bd9Sstevel@tonic-gate  *                    1 -> v1 > v2
10217c478bd9Sstevel@tonic-gate  */
ef_cmp_strings(const void * v1,const void * v2)10227c478bd9Sstevel@tonic-gate static int ef_cmp_strings(const void *v1, const void *v2)
10237c478bd9Sstevel@tonic-gate {
10247c478bd9Sstevel@tonic-gate   char * const *s1 = (char * const *) v1;
10257c478bd9Sstevel@tonic-gate   char * const *s2 = (char * const *) v2;
10267c478bd9Sstevel@tonic-gate   return strcmp(*s1, *s2);
10277c478bd9Sstevel@tonic-gate }
10287c478bd9Sstevel@tonic-gate 
10297c478bd9Sstevel@tonic-gate /*.......................................................................
10307c478bd9Sstevel@tonic-gate  * Preprocess a path, expanding ~/, ~user/ and $envvar references, using
10317c478bd9Sstevel@tonic-gate  * ef->path as a work buffer, then copy the result into a cache entry,
10327c478bd9Sstevel@tonic-gate  * and return a pointer to this copy.
10337c478bd9Sstevel@tonic-gate  *
10347c478bd9Sstevel@tonic-gate  * Input:
10357c478bd9Sstevel@tonic-gate  *  ef    ExpandFile *  The resource object of the file matcher.
10367c478bd9Sstevel@tonic-gate  *  pathlen      int    The length of the prefix of path[] to be expanded.
10377c478bd9Sstevel@tonic-gate  * Output:
10387c478bd9Sstevel@tonic-gate  *  return      char *  A pointer to a copy of the output path in the
10397c478bd9Sstevel@tonic-gate  *                      cache. On error NULL is returned, and a description
10407c478bd9Sstevel@tonic-gate  *                      of the error is left in ef->err.
10417c478bd9Sstevel@tonic-gate  */
ef_expand_special(ExpandFile * ef,const char * path,int pathlen)10427c478bd9Sstevel@tonic-gate static char *ef_expand_special(ExpandFile *ef, const char *path, int pathlen)
10437c478bd9Sstevel@tonic-gate {
10447c478bd9Sstevel@tonic-gate   int spos;      /* The index of the start of the path segment that needs */
10457c478bd9Sstevel@tonic-gate                  /*  to be copied from path[] to the output pathname. */
10467c478bd9Sstevel@tonic-gate   int ppos;      /* The index of a character in path[] */
10477c478bd9Sstevel@tonic-gate   char *pptr;    /* A pointer into the output path */
10487c478bd9Sstevel@tonic-gate   int escaped;   /* True if the previous character was a '\' */
10497c478bd9Sstevel@tonic-gate   int i;
10507c478bd9Sstevel@tonic-gate /*
10517c478bd9Sstevel@tonic-gate  * Clear the pathname buffer.
10527c478bd9Sstevel@tonic-gate  */
10537c478bd9Sstevel@tonic-gate   _pn_clear_path(ef->path);
10547c478bd9Sstevel@tonic-gate /*
10557c478bd9Sstevel@tonic-gate  * We need to perform two passes, one to expand environment variables
10567c478bd9Sstevel@tonic-gate  * and a second to do tilde expansion. This caters for the case
10577c478bd9Sstevel@tonic-gate  * where an initial dollar expansion yields a tilde expression.
10587c478bd9Sstevel@tonic-gate  */
10597c478bd9Sstevel@tonic-gate   escaped = 0;
10607c478bd9Sstevel@tonic-gate   for(spos=ppos=0; ppos < pathlen; ppos++) {
10617c478bd9Sstevel@tonic-gate     int c = path[ppos];
10627c478bd9Sstevel@tonic-gate     if(escaped) {
10637c478bd9Sstevel@tonic-gate       escaped = 0;
10647c478bd9Sstevel@tonic-gate     } else if(c == '\\') {
10657c478bd9Sstevel@tonic-gate       escaped = 1;
10667c478bd9Sstevel@tonic-gate     } else if(c == '$') {
10677c478bd9Sstevel@tonic-gate       int envlen;   /* The length of the environment variable */
10687c478bd9Sstevel@tonic-gate       char *value;  /* The value of the environment variable */
10697c478bd9Sstevel@tonic-gate /*
10707c478bd9Sstevel@tonic-gate  * Record the preceding unrecorded part of the pathname.
10717c478bd9Sstevel@tonic-gate  */
10727c478bd9Sstevel@tonic-gate       if(spos < ppos && _pn_append_to_path(ef->path, path + spos, ppos-spos, 0)
10737c478bd9Sstevel@tonic-gate 	 == NULL) {
10747c478bd9Sstevel@tonic-gate 	_err_record_msg(ef->err, "Insufficient memory to expand path",
10757c478bd9Sstevel@tonic-gate 			END_ERR_MSG);
10767c478bd9Sstevel@tonic-gate 	return NULL;
10777c478bd9Sstevel@tonic-gate       };
10787c478bd9Sstevel@tonic-gate /*
10797c478bd9Sstevel@tonic-gate  * Skip the dollar.
10807c478bd9Sstevel@tonic-gate  */
10817c478bd9Sstevel@tonic-gate       ppos++;
10827c478bd9Sstevel@tonic-gate /*
10837c478bd9Sstevel@tonic-gate  * Copy the environment variable name that follows the dollar into
10847c478bd9Sstevel@tonic-gate  * ef->envnam[], stopping if a directory separator or end of string
10857c478bd9Sstevel@tonic-gate  * is seen.
10867c478bd9Sstevel@tonic-gate  */
10877c478bd9Sstevel@tonic-gate       for(envlen=0; envlen<ENV_LEN && ppos < pathlen &&
10887c478bd9Sstevel@tonic-gate 	  strncmp(path + ppos, FS_DIR_SEP, FS_DIR_SEP_LEN); envlen++)
10897c478bd9Sstevel@tonic-gate 	ef->envnam[envlen] = path[ppos++];
10907c478bd9Sstevel@tonic-gate /*
10917c478bd9Sstevel@tonic-gate  * If the username overflowed the buffer, treat it as invalid (note that
10927c478bd9Sstevel@tonic-gate  * on most unix systems only 8 characters are allowed in a username,
10937c478bd9Sstevel@tonic-gate  * whereas our ENV_LEN is much bigger than that.
10947c478bd9Sstevel@tonic-gate  */
10957c478bd9Sstevel@tonic-gate       if(envlen >= ENV_LEN) {
10967c478bd9Sstevel@tonic-gate 	_err_record_msg(ef->err, "Environment variable name too long",
10977c478bd9Sstevel@tonic-gate 			END_ERR_MSG);
10987c478bd9Sstevel@tonic-gate 	return NULL;
10997c478bd9Sstevel@tonic-gate       };
11007c478bd9Sstevel@tonic-gate /*
11017c478bd9Sstevel@tonic-gate  * Terminate the environment variable name.
11027c478bd9Sstevel@tonic-gate  */
11037c478bd9Sstevel@tonic-gate       ef->envnam[envlen] = '\0';
11047c478bd9Sstevel@tonic-gate /*
11057c478bd9Sstevel@tonic-gate  * Lookup the value of the environment variable.
11067c478bd9Sstevel@tonic-gate  */
11077c478bd9Sstevel@tonic-gate       value = getenv(ef->envnam);
11087c478bd9Sstevel@tonic-gate       if(!value) {
11097c478bd9Sstevel@tonic-gate 	_err_record_msg(ef->err, "No expansion found for: $", ef->envnam,
11107c478bd9Sstevel@tonic-gate 			END_ERR_MSG);
11117c478bd9Sstevel@tonic-gate 	return NULL;
11127c478bd9Sstevel@tonic-gate       };
11137c478bd9Sstevel@tonic-gate /*
11147c478bd9Sstevel@tonic-gate  * Copy the value of the environment variable into the output pathname.
11157c478bd9Sstevel@tonic-gate  */
11167c478bd9Sstevel@tonic-gate       if(_pn_append_to_path(ef->path, value, -1, 0) == NULL) {
11177c478bd9Sstevel@tonic-gate 	_err_record_msg(ef->err, "Insufficient memory to expand path",
11187c478bd9Sstevel@tonic-gate 			END_ERR_MSG);
11197c478bd9Sstevel@tonic-gate 	return NULL;
11207c478bd9Sstevel@tonic-gate       };
11217c478bd9Sstevel@tonic-gate /*
11227c478bd9Sstevel@tonic-gate  * Record the start of the uncopied tail of the input pathname.
11237c478bd9Sstevel@tonic-gate  */
11247c478bd9Sstevel@tonic-gate       spos = ppos;
11257c478bd9Sstevel@tonic-gate     };
11267c478bd9Sstevel@tonic-gate   };
11277c478bd9Sstevel@tonic-gate /*
11287c478bd9Sstevel@tonic-gate  * Record the uncopied tail of the pathname.
11297c478bd9Sstevel@tonic-gate  */
11307c478bd9Sstevel@tonic-gate   if(spos < ppos && _pn_append_to_path(ef->path, path + spos, ppos-spos, 0)
11317c478bd9Sstevel@tonic-gate      == NULL) {
11327c478bd9Sstevel@tonic-gate     _err_record_msg(ef->err, "Insufficient memory to expand path", END_ERR_MSG);
11337c478bd9Sstevel@tonic-gate     return NULL;
11347c478bd9Sstevel@tonic-gate   };
11357c478bd9Sstevel@tonic-gate /*
11367c478bd9Sstevel@tonic-gate  * If the first character of the resulting pathname is a tilde,
11377c478bd9Sstevel@tonic-gate  * then attempt to substitute the home directory of the specified user.
11387c478bd9Sstevel@tonic-gate  */
11397c478bd9Sstevel@tonic-gate   pptr = ef->path->name;
11407c478bd9Sstevel@tonic-gate   if(*pptr == '~' && path[0] != '\\') {
11417c478bd9Sstevel@tonic-gate     int usrlen;           /* The length of the username following the tilde */
11427c478bd9Sstevel@tonic-gate     const char *homedir;  /* The home directory of the user */
11437c478bd9Sstevel@tonic-gate     int homelen;          /* The length of the home directory string */
11447c478bd9Sstevel@tonic-gate     int plen;             /* The current length of the path */
11457c478bd9Sstevel@tonic-gate     int skip=0;           /* The number of characters to skip after the ~user */
11467c478bd9Sstevel@tonic-gate /*
11477c478bd9Sstevel@tonic-gate  * Get the current length of the output path.
11487c478bd9Sstevel@tonic-gate  */
11497c478bd9Sstevel@tonic-gate     plen = strlen(ef->path->name);
11507c478bd9Sstevel@tonic-gate /*
11517c478bd9Sstevel@tonic-gate  * Skip the tilde.
11527c478bd9Sstevel@tonic-gate  */
11537c478bd9Sstevel@tonic-gate     pptr++;
11547c478bd9Sstevel@tonic-gate /*
11557c478bd9Sstevel@tonic-gate  * Copy the optional username that follows the tilde into ef->usrnam[].
11567c478bd9Sstevel@tonic-gate  */
11577c478bd9Sstevel@tonic-gate     for(usrlen=0; usrlen<USR_LEN && *pptr &&
11587c478bd9Sstevel@tonic-gate 	strncmp(pptr, FS_DIR_SEP, FS_DIR_SEP_LEN); usrlen++)
11597c478bd9Sstevel@tonic-gate       ef->usrnam[usrlen] = *pptr++;
11607c478bd9Sstevel@tonic-gate /*
11617c478bd9Sstevel@tonic-gate  * If the username overflowed the buffer, treat it as invalid (note that
11627c478bd9Sstevel@tonic-gate  * on most unix systems only 8 characters are allowed in a username,
11637c478bd9Sstevel@tonic-gate  * whereas our USR_LEN is much bigger than that.
11647c478bd9Sstevel@tonic-gate  */
11657c478bd9Sstevel@tonic-gate     if(usrlen >= USR_LEN) {
11667c478bd9Sstevel@tonic-gate       _err_record_msg(ef->err, "Username too long", END_ERR_MSG);
11677c478bd9Sstevel@tonic-gate       return NULL;
11687c478bd9Sstevel@tonic-gate     };
11697c478bd9Sstevel@tonic-gate /*
11707c478bd9Sstevel@tonic-gate  * Terminate the username string.
11717c478bd9Sstevel@tonic-gate  */
11727c478bd9Sstevel@tonic-gate     ef->usrnam[usrlen] = '\0';
11737c478bd9Sstevel@tonic-gate /*
11747c478bd9Sstevel@tonic-gate  * Lookup the home directory of the user.
11757c478bd9Sstevel@tonic-gate  */
11767c478bd9Sstevel@tonic-gate     homedir = _hd_lookup_home_dir(ef->home, ef->usrnam);
11777c478bd9Sstevel@tonic-gate     if(!homedir) {
11787c478bd9Sstevel@tonic-gate       _err_record_msg(ef->err, _hd_last_home_dir_error(ef->home), END_ERR_MSG);
11797c478bd9Sstevel@tonic-gate       return NULL;
11807c478bd9Sstevel@tonic-gate     };
11817c478bd9Sstevel@tonic-gate     homelen = strlen(homedir);
11827c478bd9Sstevel@tonic-gate /*
11837c478bd9Sstevel@tonic-gate  * ~user and ~ are usually followed by a directory separator to
11847c478bd9Sstevel@tonic-gate  * separate them from the file contained in the home directory.
11857c478bd9Sstevel@tonic-gate  * If the home directory is the root directory, then we don't want
11867c478bd9Sstevel@tonic-gate  * to follow the home directory by a directory separator, so we must
11877c478bd9Sstevel@tonic-gate  * erase it.
11887c478bd9Sstevel@tonic-gate  */
11897c478bd9Sstevel@tonic-gate     if(strcmp(homedir, FS_ROOT_DIR) == 0 &&
11907c478bd9Sstevel@tonic-gate        strncmp(pptr, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0) {
11917c478bd9Sstevel@tonic-gate       skip = FS_DIR_SEP_LEN;
11927c478bd9Sstevel@tonic-gate     };
11937c478bd9Sstevel@tonic-gate /*
11947c478bd9Sstevel@tonic-gate  * If needed, increase the size of the pathname buffer to allow it
11957c478bd9Sstevel@tonic-gate  * to accomodate the home directory instead of the tilde expression.
11967c478bd9Sstevel@tonic-gate  * Note that pptr may not be valid after this call.
11977c478bd9Sstevel@tonic-gate  */
11987c478bd9Sstevel@tonic-gate     if(_pn_resize_path(ef->path, plen - usrlen - 1 - skip + homelen)==NULL) {
11997c478bd9Sstevel@tonic-gate       _err_record_msg(ef->err, "Insufficient memory to expand filename",
12007c478bd9Sstevel@tonic-gate 		      END_ERR_MSG);
12017c478bd9Sstevel@tonic-gate       return NULL;
12027c478bd9Sstevel@tonic-gate     };
12037c478bd9Sstevel@tonic-gate /*
12047c478bd9Sstevel@tonic-gate  * Move the part of the pathname that follows the tilde expression to
12057c478bd9Sstevel@tonic-gate  * the end of where the home directory will need to be inserted.
12067c478bd9Sstevel@tonic-gate  */
12077c478bd9Sstevel@tonic-gate     memmove(ef->path->name + homelen,
12087c478bd9Sstevel@tonic-gate 	    ef->path->name + 1 + usrlen + skip, plen - usrlen - 1 - skip+1);
12097c478bd9Sstevel@tonic-gate /*
12107c478bd9Sstevel@tonic-gate  * Write the home directory at the beginning of the string.
12117c478bd9Sstevel@tonic-gate  */
12127c478bd9Sstevel@tonic-gate     for(i=0; i<homelen; i++)
12137c478bd9Sstevel@tonic-gate       ef->path->name[i] = homedir[i];
12147c478bd9Sstevel@tonic-gate   };
12157c478bd9Sstevel@tonic-gate /*
12167c478bd9Sstevel@tonic-gate  * Copy the result into the cache, and return a pointer to the copy.
12177c478bd9Sstevel@tonic-gate  */
12187c478bd9Sstevel@tonic-gate   return ef_cache_pathname(ef, ef->path->name, 0);
12197c478bd9Sstevel@tonic-gate }
12207c478bd9Sstevel@tonic-gate 
12217c478bd9Sstevel@tonic-gate /*.......................................................................
12227c478bd9Sstevel@tonic-gate  * Return a description of the last path-expansion error that occurred.
12237c478bd9Sstevel@tonic-gate  *
12247c478bd9Sstevel@tonic-gate  * Input:
12257c478bd9Sstevel@tonic-gate  *  ef     ExpandFile *   The path-expansion resource object.
12267c478bd9Sstevel@tonic-gate  * Output:
12277c478bd9Sstevel@tonic-gate  *  return       char *   The description of the last error.
12287c478bd9Sstevel@tonic-gate  */
ef_last_error(ExpandFile * ef)12297c478bd9Sstevel@tonic-gate const char *ef_last_error(ExpandFile *ef)
12307c478bd9Sstevel@tonic-gate {
12317c478bd9Sstevel@tonic-gate   return ef ? _err_get_msg(ef->err) : "NULL ExpandFile argument";
12327c478bd9Sstevel@tonic-gate }
12337c478bd9Sstevel@tonic-gate 
12347c478bd9Sstevel@tonic-gate /*.......................................................................
12357c478bd9Sstevel@tonic-gate  * Print out an array of matching files.
12367c478bd9Sstevel@tonic-gate  *
12377c478bd9Sstevel@tonic-gate  * Input:
12387c478bd9Sstevel@tonic-gate  *  result  FileExpansion *   The container of the sorted array of
12397c478bd9Sstevel@tonic-gate  *                            expansions.
12407c478bd9Sstevel@tonic-gate  *  fp               FILE *   The output stream to write to.
12417c478bd9Sstevel@tonic-gate  *  term_width        int     The width of the terminal.
12427c478bd9Sstevel@tonic-gate  * Output:
12437c478bd9Sstevel@tonic-gate  *  return            int     0 - OK.
12447c478bd9Sstevel@tonic-gate  *                            1 - Error.
12457c478bd9Sstevel@tonic-gate  */
ef_list_expansions(FileExpansion * result,FILE * fp,int term_width)12467c478bd9Sstevel@tonic-gate int ef_list_expansions(FileExpansion *result, FILE *fp, int term_width)
12477c478bd9Sstevel@tonic-gate {
12487c478bd9Sstevel@tonic-gate   return _ef_output_expansions(result, _io_write_stdio, fp, term_width);
12497c478bd9Sstevel@tonic-gate }
12507c478bd9Sstevel@tonic-gate 
12517c478bd9Sstevel@tonic-gate /*.......................................................................
12527c478bd9Sstevel@tonic-gate  * Print out an array of matching files via a callback.
12537c478bd9Sstevel@tonic-gate  *
12547c478bd9Sstevel@tonic-gate  * Input:
12557c478bd9Sstevel@tonic-gate  *  result  FileExpansion *  The container of the sorted array of
12567c478bd9Sstevel@tonic-gate  *                           expansions.
12577c478bd9Sstevel@tonic-gate  *  write_fn    GlWriteFn *  The function to call to write the
12587c478bd9Sstevel@tonic-gate  *                           expansions or 0 to discard the output.
12597c478bd9Sstevel@tonic-gate  *  data             void *  Anonymous data to pass to write_fn().
12607c478bd9Sstevel@tonic-gate  *  term_width        int    The width of the terminal.
12617c478bd9Sstevel@tonic-gate  * Output:
12627c478bd9Sstevel@tonic-gate  *  return            int    0 - OK.
12637c478bd9Sstevel@tonic-gate  *                           1 - Error.
12647c478bd9Sstevel@tonic-gate  */
_ef_output_expansions(FileExpansion * result,GlWriteFn * write_fn,void * data,int term_width)12657c478bd9Sstevel@tonic-gate int _ef_output_expansions(FileExpansion *result, GlWriteFn *write_fn,
12667c478bd9Sstevel@tonic-gate 			  void *data, int term_width)
12677c478bd9Sstevel@tonic-gate {
12687c478bd9Sstevel@tonic-gate   EfListFormat fmt; /* List formatting information */
12697c478bd9Sstevel@tonic-gate   int lnum;          /* The sequential number of the line to print next */
12707c478bd9Sstevel@tonic-gate /*
12717c478bd9Sstevel@tonic-gate  * Not enough space to list anything?
12727c478bd9Sstevel@tonic-gate  */
12737c478bd9Sstevel@tonic-gate   if(term_width < 1)
12747c478bd9Sstevel@tonic-gate     return 0;
12757c478bd9Sstevel@tonic-gate /*
12767c478bd9Sstevel@tonic-gate  * Do we have a callback to write via, and any expansions to be listed?
12777c478bd9Sstevel@tonic-gate  */
12787c478bd9Sstevel@tonic-gate   if(write_fn && result && result->nfile>0) {
12797c478bd9Sstevel@tonic-gate /*
12807c478bd9Sstevel@tonic-gate  * Work out how to arrange the listing into fixed sized columns.
12817c478bd9Sstevel@tonic-gate  */
12827c478bd9Sstevel@tonic-gate     ef_plan_listing(result, term_width, &fmt);
12837c478bd9Sstevel@tonic-gate /*
12847c478bd9Sstevel@tonic-gate  * Print the listing to the specified stream.
12857c478bd9Sstevel@tonic-gate  */
12867c478bd9Sstevel@tonic-gate     for(lnum=0; lnum < fmt.nline; lnum++) {
12877c478bd9Sstevel@tonic-gate       if(ef_format_line(result, &fmt, lnum, write_fn, data))
12887c478bd9Sstevel@tonic-gate 	return 1;
12897c478bd9Sstevel@tonic-gate     };
12907c478bd9Sstevel@tonic-gate   };
12917c478bd9Sstevel@tonic-gate   return 0;
12927c478bd9Sstevel@tonic-gate }
12937c478bd9Sstevel@tonic-gate 
12947c478bd9Sstevel@tonic-gate /*.......................................................................
12957c478bd9Sstevel@tonic-gate  * Work out how to arrange a given array of completions into a listing
12967c478bd9Sstevel@tonic-gate  * of one or more fixed size columns.
12977c478bd9Sstevel@tonic-gate  *
12987c478bd9Sstevel@tonic-gate  * Input:
12997c478bd9Sstevel@tonic-gate  *  result   FileExpansion *   The set of completions to be listed.
13007c478bd9Sstevel@tonic-gate  *  term_width         int     The width of the terminal. A lower limit of
13017c478bd9Sstevel@tonic-gate  *                             zero is quietly enforced.
13027c478bd9Sstevel@tonic-gate  * Input/Output:
13037c478bd9Sstevel@tonic-gate  *  fmt       EfListFormat *   The formatting information will be assigned
13047c478bd9Sstevel@tonic-gate  *                             to the members of *fmt.
13057c478bd9Sstevel@tonic-gate  */
ef_plan_listing(FileExpansion * result,int term_width,EfListFormat * fmt)13067c478bd9Sstevel@tonic-gate static void ef_plan_listing(FileExpansion *result, int term_width,
13077c478bd9Sstevel@tonic-gate 			    EfListFormat *fmt)
13087c478bd9Sstevel@tonic-gate {
13097c478bd9Sstevel@tonic-gate   int maxlen;    /* The length of the longest matching string */
13107c478bd9Sstevel@tonic-gate   int i;
13117c478bd9Sstevel@tonic-gate /*
13127c478bd9Sstevel@tonic-gate  * Ensure that term_width >= 0.
13137c478bd9Sstevel@tonic-gate  */
13147c478bd9Sstevel@tonic-gate   if(term_width < 0)
13157c478bd9Sstevel@tonic-gate     term_width = 0;
13167c478bd9Sstevel@tonic-gate /*
13177c478bd9Sstevel@tonic-gate  * Start by assuming the worst case, that either nothing will fit
13187c478bd9Sstevel@tonic-gate  * on the screen, or that there are no matches to be listed.
13197c478bd9Sstevel@tonic-gate  */
13207c478bd9Sstevel@tonic-gate   fmt->term_width = term_width;
13217c478bd9Sstevel@tonic-gate   fmt->column_width = 0;
13227c478bd9Sstevel@tonic-gate   fmt->nline = fmt->ncol = 0;
13237c478bd9Sstevel@tonic-gate /*
13247c478bd9Sstevel@tonic-gate  * Work out the maximum length of the matching strings.
13257c478bd9Sstevel@tonic-gate  */
13267c478bd9Sstevel@tonic-gate   maxlen = 0;
13277c478bd9Sstevel@tonic-gate   for(i=0; i<result->nfile; i++) {
13287c478bd9Sstevel@tonic-gate     int len = strlen(result->files[i]);
13297c478bd9Sstevel@tonic-gate     if(len > maxlen)
13307c478bd9Sstevel@tonic-gate       maxlen = len;
13317c478bd9Sstevel@tonic-gate   };
13327c478bd9Sstevel@tonic-gate /*
13337c478bd9Sstevel@tonic-gate  * Nothing to list?
13347c478bd9Sstevel@tonic-gate  */
13357c478bd9Sstevel@tonic-gate   if(maxlen == 0)
13367c478bd9Sstevel@tonic-gate     return;
13377c478bd9Sstevel@tonic-gate /*
13387c478bd9Sstevel@tonic-gate  * Split the available terminal width into columns of
13397c478bd9Sstevel@tonic-gate  * maxlen + EF_COL_SEP characters.
13407c478bd9Sstevel@tonic-gate  */
13417c478bd9Sstevel@tonic-gate   fmt->column_width = maxlen;
13427c478bd9Sstevel@tonic-gate   fmt->ncol = fmt->term_width / (fmt->column_width + EF_COL_SEP);
13437c478bd9Sstevel@tonic-gate /*
13447c478bd9Sstevel@tonic-gate  * If the column width is greater than the terminal width, zero columns
13457c478bd9Sstevel@tonic-gate  * will have been selected. Set a lower limit of one column. Leave it
13467c478bd9Sstevel@tonic-gate  * up to the caller how to deal with completions who's widths exceed
13477c478bd9Sstevel@tonic-gate  * the available terminal width.
13487c478bd9Sstevel@tonic-gate  */
13497c478bd9Sstevel@tonic-gate   if(fmt->ncol < 1)
13507c478bd9Sstevel@tonic-gate     fmt->ncol = 1;
13517c478bd9Sstevel@tonic-gate /*
13527c478bd9Sstevel@tonic-gate  * How many lines of output will be needed?
13537c478bd9Sstevel@tonic-gate  */
13547c478bd9Sstevel@tonic-gate   fmt->nline = (result->nfile + fmt->ncol - 1) / fmt->ncol;
13557c478bd9Sstevel@tonic-gate   return;
13567c478bd9Sstevel@tonic-gate }
13577c478bd9Sstevel@tonic-gate 
13587c478bd9Sstevel@tonic-gate /*.......................................................................
13597c478bd9Sstevel@tonic-gate  * Render one line of a multi-column listing of completions, using a
13607c478bd9Sstevel@tonic-gate  * callback function to pass the output to an arbitrary destination.
13617c478bd9Sstevel@tonic-gate  *
13627c478bd9Sstevel@tonic-gate  * Input:
13637c478bd9Sstevel@tonic-gate  *  result   FileExpansion *  The container of the sorted array of
13647c478bd9Sstevel@tonic-gate  *                            completions.
13657c478bd9Sstevel@tonic-gate  *  fmt       EfListFormat *  Formatting information.
13667c478bd9Sstevel@tonic-gate  *  lnum               int    The index of the line to print, starting
13677c478bd9Sstevel@tonic-gate  *                            from 0, and incrementing until the return
13687c478bd9Sstevel@tonic-gate  *                            value indicates that there is nothing more
13697c478bd9Sstevel@tonic-gate  *                            to be printed.
13707c478bd9Sstevel@tonic-gate  *  write_fn     GlWriteFn *  The function to call to write the line, or
13717c478bd9Sstevel@tonic-gate  *                            0 to discard the output.
13727c478bd9Sstevel@tonic-gate  *  data              void *  Anonymous data to pass to write_fn().
13737c478bd9Sstevel@tonic-gate  * Output:
13747c478bd9Sstevel@tonic-gate  *  return             int    0 - Line printed ok.
13757c478bd9Sstevel@tonic-gate  *                            1 - Nothing to print.
13767c478bd9Sstevel@tonic-gate  */
ef_format_line(FileExpansion * result,EfListFormat * fmt,int lnum,GlWriteFn * write_fn,void * data)13777c478bd9Sstevel@tonic-gate static int ef_format_line(FileExpansion *result, EfListFormat *fmt, int lnum,
13787c478bd9Sstevel@tonic-gate 			  GlWriteFn *write_fn, void *data)
13797c478bd9Sstevel@tonic-gate {
13807c478bd9Sstevel@tonic-gate   int col;             /* The index of the list column being output */
13817c478bd9Sstevel@tonic-gate /*
13827c478bd9Sstevel@tonic-gate  * If the line index is out of bounds, there is nothing to be written.
13837c478bd9Sstevel@tonic-gate  */
13847c478bd9Sstevel@tonic-gate   if(lnum < 0 || lnum >= fmt->nline)
13857c478bd9Sstevel@tonic-gate     return 1;
13867c478bd9Sstevel@tonic-gate /*
13877c478bd9Sstevel@tonic-gate  * If no output function has been provided, return as though the line
13887c478bd9Sstevel@tonic-gate  * had been printed.
13897c478bd9Sstevel@tonic-gate  */
13907c478bd9Sstevel@tonic-gate   if(!write_fn)
13917c478bd9Sstevel@tonic-gate     return 0;
13927c478bd9Sstevel@tonic-gate /*
13937c478bd9Sstevel@tonic-gate  * Print the matches in 'ncol' columns, sorted in line order within each
13947c478bd9Sstevel@tonic-gate  * column.
13957c478bd9Sstevel@tonic-gate  */
13967c478bd9Sstevel@tonic-gate   for(col=0; col < fmt->ncol; col++) {
13977c478bd9Sstevel@tonic-gate     int m = col*fmt->nline + lnum;
13987c478bd9Sstevel@tonic-gate /*
13997c478bd9Sstevel@tonic-gate  * Is there another match to be written? Note that in general
14007c478bd9Sstevel@tonic-gate  * the last line of a listing will have fewer filled columns
14017c478bd9Sstevel@tonic-gate  * than the initial lines.
14027c478bd9Sstevel@tonic-gate  */
14037c478bd9Sstevel@tonic-gate     if(m < result->nfile) {
14047c478bd9Sstevel@tonic-gate       char *file = result->files[m];
14057c478bd9Sstevel@tonic-gate /*
14067c478bd9Sstevel@tonic-gate  * How long are the completion and type-suffix strings?
14077c478bd9Sstevel@tonic-gate  */
14087c478bd9Sstevel@tonic-gate       int flen = strlen(file);
14097c478bd9Sstevel@tonic-gate /*
14107c478bd9Sstevel@tonic-gate  * Write the completion string.
14117c478bd9Sstevel@tonic-gate  */
14127c478bd9Sstevel@tonic-gate       if(write_fn(data, file, flen) != flen)
14137c478bd9Sstevel@tonic-gate 	return 1;
14147c478bd9Sstevel@tonic-gate /*
14157c478bd9Sstevel@tonic-gate  * If another column follows the current one, pad to its start with spaces.
14167c478bd9Sstevel@tonic-gate  */
14177c478bd9Sstevel@tonic-gate       if(col+1 < fmt->ncol) {
14187c478bd9Sstevel@tonic-gate /*
14197c478bd9Sstevel@tonic-gate  * The following constant string of spaces is used to pad the output.
14207c478bd9Sstevel@tonic-gate  */
14217c478bd9Sstevel@tonic-gate 	static const char spaces[] = "                    ";
14227c478bd9Sstevel@tonic-gate 	static const int nspace = sizeof(spaces) - 1;
14237c478bd9Sstevel@tonic-gate /*
14247c478bd9Sstevel@tonic-gate  * Pad to the next column, using as few sub-strings of the spaces[]
14257c478bd9Sstevel@tonic-gate  * array as possible.
14267c478bd9Sstevel@tonic-gate  */
14277c478bd9Sstevel@tonic-gate 	int npad = fmt->column_width + EF_COL_SEP - flen;
14287c478bd9Sstevel@tonic-gate 	while(npad>0) {
14297c478bd9Sstevel@tonic-gate 	  int n = npad > nspace ? nspace : npad;
14307c478bd9Sstevel@tonic-gate 	  if(write_fn(data, spaces + nspace - n, n) != n)
14317c478bd9Sstevel@tonic-gate 	    return 1;
14327c478bd9Sstevel@tonic-gate 	  npad -= n;
14337c478bd9Sstevel@tonic-gate 	};
14347c478bd9Sstevel@tonic-gate       };
14357c478bd9Sstevel@tonic-gate     };
14367c478bd9Sstevel@tonic-gate   };
14377c478bd9Sstevel@tonic-gate /*
14387c478bd9Sstevel@tonic-gate  * Start a new line.
14397c478bd9Sstevel@tonic-gate  */
14407c478bd9Sstevel@tonic-gate   {
14417c478bd9Sstevel@tonic-gate     char s[] = "\r\n";
14427c478bd9Sstevel@tonic-gate     int n = strlen(s);
14437c478bd9Sstevel@tonic-gate     if(write_fn(data, s, n) != n)
14447c478bd9Sstevel@tonic-gate       return 1;
14457c478bd9Sstevel@tonic-gate   };
14467c478bd9Sstevel@tonic-gate   return 0;
14477c478bd9Sstevel@tonic-gate }
14487c478bd9Sstevel@tonic-gate 
14497c478bd9Sstevel@tonic-gate #endif  /* ifndef WITHOUT_FILE_SYSTEM */
1450