1*7c478bd9Sstevel@tonic-gate /* 2*7c478bd9Sstevel@tonic-gate * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. 3*7c478bd9Sstevel@tonic-gate * 4*7c478bd9Sstevel@tonic-gate * All rights reserved. 5*7c478bd9Sstevel@tonic-gate * 6*7c478bd9Sstevel@tonic-gate * Permission is hereby granted, free of charge, to any person obtaining a 7*7c478bd9Sstevel@tonic-gate * copy of this software and associated documentation files (the 8*7c478bd9Sstevel@tonic-gate * "Software"), to deal in the Software without restriction, including 9*7c478bd9Sstevel@tonic-gate * without limitation the rights to use, copy, modify, merge, publish, 10*7c478bd9Sstevel@tonic-gate * distribute, and/or sell copies of the Software, and to permit persons 11*7c478bd9Sstevel@tonic-gate * to whom the Software is furnished to do so, provided that the above 12*7c478bd9Sstevel@tonic-gate * copyright notice(s) and this permission notice appear in all copies of 13*7c478bd9Sstevel@tonic-gate * the Software and that both the above copyright notice(s) and this 14*7c478bd9Sstevel@tonic-gate * permission notice appear in supporting documentation. 15*7c478bd9Sstevel@tonic-gate * 16*7c478bd9Sstevel@tonic-gate * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17*7c478bd9Sstevel@tonic-gate * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18*7c478bd9Sstevel@tonic-gate * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 19*7c478bd9Sstevel@tonic-gate * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 20*7c478bd9Sstevel@tonic-gate * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL 21*7c478bd9Sstevel@tonic-gate * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING 22*7c478bd9Sstevel@tonic-gate * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, 23*7c478bd9Sstevel@tonic-gate * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION 24*7c478bd9Sstevel@tonic-gate * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 25*7c478bd9Sstevel@tonic-gate * 26*7c478bd9Sstevel@tonic-gate * Except as contained in this notice, the name of a copyright holder 27*7c478bd9Sstevel@tonic-gate * shall not be used in advertising or otherwise to promote the sale, use 28*7c478bd9Sstevel@tonic-gate * or other dealings in this Software without prior written authorization 29*7c478bd9Sstevel@tonic-gate * of the copyright holder. 30*7c478bd9Sstevel@tonic-gate */ 31*7c478bd9Sstevel@tonic-gate 32*7c478bd9Sstevel@tonic-gate #pragma ident "%Z%%M% %I% %E% SMI" 33*7c478bd9Sstevel@tonic-gate 34*7c478bd9Sstevel@tonic-gate /* 35*7c478bd9Sstevel@tonic-gate * If file-system access is to be excluded, this module has no function, 36*7c478bd9Sstevel@tonic-gate * so all of its code should be excluded. 37*7c478bd9Sstevel@tonic-gate */ 38*7c478bd9Sstevel@tonic-gate #ifndef WITHOUT_FILE_SYSTEM 39*7c478bd9Sstevel@tonic-gate 40*7c478bd9Sstevel@tonic-gate #include <stdio.h> 41*7c478bd9Sstevel@tonic-gate #include <stdlib.h> 42*7c478bd9Sstevel@tonic-gate #include <string.h> 43*7c478bd9Sstevel@tonic-gate #include <errno.h> 44*7c478bd9Sstevel@tonic-gate 45*7c478bd9Sstevel@tonic-gate #include "freelist.h" 46*7c478bd9Sstevel@tonic-gate #include "direader.h" 47*7c478bd9Sstevel@tonic-gate #include "pathutil.h" 48*7c478bd9Sstevel@tonic-gate #include "homedir.h" 49*7c478bd9Sstevel@tonic-gate #include "stringrp.h" 50*7c478bd9Sstevel@tonic-gate #include "libtecla.h" 51*7c478bd9Sstevel@tonic-gate #include "ioutil.h" 52*7c478bd9Sstevel@tonic-gate #include "expand.h" 53*7c478bd9Sstevel@tonic-gate #include "errmsg.h" 54*7c478bd9Sstevel@tonic-gate 55*7c478bd9Sstevel@tonic-gate /* 56*7c478bd9Sstevel@tonic-gate * Specify the number of elements to extend the files[] array by 57*7c478bd9Sstevel@tonic-gate * when it proves to be too small. This also sets the initial size 58*7c478bd9Sstevel@tonic-gate * of the array. 59*7c478bd9Sstevel@tonic-gate */ 60*7c478bd9Sstevel@tonic-gate #define MATCH_BLK_FACT 256 61*7c478bd9Sstevel@tonic-gate 62*7c478bd9Sstevel@tonic-gate /* 63*7c478bd9Sstevel@tonic-gate * A list of directory iterators is maintained using nodes of the 64*7c478bd9Sstevel@tonic-gate * following form. 65*7c478bd9Sstevel@tonic-gate */ 66*7c478bd9Sstevel@tonic-gate typedef struct DirNode DirNode; 67*7c478bd9Sstevel@tonic-gate struct DirNode { 68*7c478bd9Sstevel@tonic-gate DirNode *next; /* The next directory in the list */ 69*7c478bd9Sstevel@tonic-gate DirNode *prev; /* The node that precedes this node in the list */ 70*7c478bd9Sstevel@tonic-gate DirReader *dr; /* The directory reader object */ 71*7c478bd9Sstevel@tonic-gate }; 72*7c478bd9Sstevel@tonic-gate 73*7c478bd9Sstevel@tonic-gate typedef struct { 74*7c478bd9Sstevel@tonic-gate FreeList *mem; /* Memory for DirNode list nodes */ 75*7c478bd9Sstevel@tonic-gate DirNode *head; /* The head of the list of used and unused cache nodes */ 76*7c478bd9Sstevel@tonic-gate DirNode *next; /* The next unused node between head and tail */ 77*7c478bd9Sstevel@tonic-gate DirNode *tail; /* The tail of the list of unused cache nodes */ 78*7c478bd9Sstevel@tonic-gate } DirCache; 79*7c478bd9Sstevel@tonic-gate 80*7c478bd9Sstevel@tonic-gate /* 81*7c478bd9Sstevel@tonic-gate * Specify how many directory cache nodes to allocate at a time. 82*7c478bd9Sstevel@tonic-gate */ 83*7c478bd9Sstevel@tonic-gate #define DIR_CACHE_BLK 20 84*7c478bd9Sstevel@tonic-gate 85*7c478bd9Sstevel@tonic-gate /* 86*7c478bd9Sstevel@tonic-gate * Set the maximum length allowed for usernames. 87*7c478bd9Sstevel@tonic-gate */ 88*7c478bd9Sstevel@tonic-gate #define USR_LEN 100 89*7c478bd9Sstevel@tonic-gate 90*7c478bd9Sstevel@tonic-gate /* 91*7c478bd9Sstevel@tonic-gate * Set the maximum length allowed for environment variable names. 92*7c478bd9Sstevel@tonic-gate */ 93*7c478bd9Sstevel@tonic-gate #define ENV_LEN 100 94*7c478bd9Sstevel@tonic-gate 95*7c478bd9Sstevel@tonic-gate /* 96*7c478bd9Sstevel@tonic-gate * Set the default number of spaces place between columns when listing 97*7c478bd9Sstevel@tonic-gate * a set of expansions. 98*7c478bd9Sstevel@tonic-gate */ 99*7c478bd9Sstevel@tonic-gate #define EF_COL_SEP 2 100*7c478bd9Sstevel@tonic-gate 101*7c478bd9Sstevel@tonic-gate struct ExpandFile { 102*7c478bd9Sstevel@tonic-gate ErrMsg *err; /* The error reporting buffer */ 103*7c478bd9Sstevel@tonic-gate StringGroup *sg; /* A list of string segments in which */ 104*7c478bd9Sstevel@tonic-gate /* matching filenames are stored. */ 105*7c478bd9Sstevel@tonic-gate DirCache cache; /* The cache of directory reader objects */ 106*7c478bd9Sstevel@tonic-gate PathName *path; /* The pathname being matched */ 107*7c478bd9Sstevel@tonic-gate HomeDir *home; /* Home-directory lookup object */ 108*7c478bd9Sstevel@tonic-gate int files_dim; /* The allocated dimension of result.files[] */ 109*7c478bd9Sstevel@tonic-gate char usrnam[USR_LEN+1]; /* A user name */ 110*7c478bd9Sstevel@tonic-gate char envnam[ENV_LEN+1]; /* An environment variable name */ 111*7c478bd9Sstevel@tonic-gate FileExpansion result; /* The container used to return the results of */ 112*7c478bd9Sstevel@tonic-gate /* expanding a path. */ 113*7c478bd9Sstevel@tonic-gate }; 114*7c478bd9Sstevel@tonic-gate 115*7c478bd9Sstevel@tonic-gate static int ef_record_pathname(ExpandFile *ef, const char *pathname, 116*7c478bd9Sstevel@tonic-gate int remove_escapes); 117*7c478bd9Sstevel@tonic-gate static char *ef_cache_pathname(ExpandFile *ef, const char *pathname, 118*7c478bd9Sstevel@tonic-gate int remove_escapes); 119*7c478bd9Sstevel@tonic-gate static void ef_clear_files(ExpandFile *ef); 120*7c478bd9Sstevel@tonic-gate 121*7c478bd9Sstevel@tonic-gate static DirNode *ef_open_dir(ExpandFile *ef, const char *pathname); 122*7c478bd9Sstevel@tonic-gate static DirNode *ef_close_dir(ExpandFile *ef, DirNode *node); 123*7c478bd9Sstevel@tonic-gate static char *ef_expand_special(ExpandFile *ef, const char *path, int pathlen); 124*7c478bd9Sstevel@tonic-gate static int ef_match_relative_pathname(ExpandFile *ef, DirReader *dr, 125*7c478bd9Sstevel@tonic-gate const char *pattern, int separate); 126*7c478bd9Sstevel@tonic-gate static int ef_matches_range(int c, const char *pattern, const char **endp); 127*7c478bd9Sstevel@tonic-gate static int ef_string_matches_pattern(const char *file, const char *pattern, 128*7c478bd9Sstevel@tonic-gate int xplicit, const char *nextp); 129*7c478bd9Sstevel@tonic-gate static int ef_cmp_strings(const void *v1, const void *v2); 130*7c478bd9Sstevel@tonic-gate 131*7c478bd9Sstevel@tonic-gate /* 132*7c478bd9Sstevel@tonic-gate * Encapsulate the formatting information needed to layout a 133*7c478bd9Sstevel@tonic-gate * multi-column listing of expansions. 134*7c478bd9Sstevel@tonic-gate */ 135*7c478bd9Sstevel@tonic-gate typedef struct { 136*7c478bd9Sstevel@tonic-gate int term_width; /* The width of the terminal (characters) */ 137*7c478bd9Sstevel@tonic-gate int column_width; /* The number of characters within in each column. */ 138*7c478bd9Sstevel@tonic-gate int ncol; /* The number of columns needed */ 139*7c478bd9Sstevel@tonic-gate int nline; /* The number of lines needed */ 140*7c478bd9Sstevel@tonic-gate } EfListFormat; 141*7c478bd9Sstevel@tonic-gate 142*7c478bd9Sstevel@tonic-gate /* 143*7c478bd9Sstevel@tonic-gate * Given the current terminal width, and a list of file expansions, 144*7c478bd9Sstevel@tonic-gate * determine how to best use the terminal width to display a multi-column 145*7c478bd9Sstevel@tonic-gate * listing of expansions. 146*7c478bd9Sstevel@tonic-gate */ 147*7c478bd9Sstevel@tonic-gate static void ef_plan_listing(FileExpansion *result, int term_width, 148*7c478bd9Sstevel@tonic-gate EfListFormat *fmt); 149*7c478bd9Sstevel@tonic-gate 150*7c478bd9Sstevel@tonic-gate /* 151*7c478bd9Sstevel@tonic-gate * Display a given line of a multi-column list of file-expansions. 152*7c478bd9Sstevel@tonic-gate */ 153*7c478bd9Sstevel@tonic-gate static int ef_format_line(FileExpansion *result, EfListFormat *fmt, int lnum, 154*7c478bd9Sstevel@tonic-gate GlWriteFn *write_fn, void *data); 155*7c478bd9Sstevel@tonic-gate 156*7c478bd9Sstevel@tonic-gate /*....................................................................... 157*7c478bd9Sstevel@tonic-gate * Create the resources needed to expand filenames. 158*7c478bd9Sstevel@tonic-gate * 159*7c478bd9Sstevel@tonic-gate * Output: 160*7c478bd9Sstevel@tonic-gate * return ExpandFile * The new object, or NULL on error. 161*7c478bd9Sstevel@tonic-gate */ 162*7c478bd9Sstevel@tonic-gate ExpandFile *new_ExpandFile(void) 163*7c478bd9Sstevel@tonic-gate { 164*7c478bd9Sstevel@tonic-gate ExpandFile *ef; /* The object to be returned */ 165*7c478bd9Sstevel@tonic-gate /* 166*7c478bd9Sstevel@tonic-gate * Allocate the container. 167*7c478bd9Sstevel@tonic-gate */ 168*7c478bd9Sstevel@tonic-gate ef = (ExpandFile *) malloc(sizeof(ExpandFile)); 169*7c478bd9Sstevel@tonic-gate if(!ef) { 170*7c478bd9Sstevel@tonic-gate errno = ENOMEM; 171*7c478bd9Sstevel@tonic-gate return NULL; 172*7c478bd9Sstevel@tonic-gate }; 173*7c478bd9Sstevel@tonic-gate /* 174*7c478bd9Sstevel@tonic-gate * Before attempting any operation that might fail, initialize the 175*7c478bd9Sstevel@tonic-gate * container at least up to the point at which it can safely be passed 176*7c478bd9Sstevel@tonic-gate * to del_ExpandFile(). 177*7c478bd9Sstevel@tonic-gate */ 178*7c478bd9Sstevel@tonic-gate ef->err = NULL; 179*7c478bd9Sstevel@tonic-gate ef->sg = NULL; 180*7c478bd9Sstevel@tonic-gate ef->cache.mem = NULL; 181*7c478bd9Sstevel@tonic-gate ef->cache.head = NULL; 182*7c478bd9Sstevel@tonic-gate ef->cache.next = NULL; 183*7c478bd9Sstevel@tonic-gate ef->cache.tail = NULL; 184*7c478bd9Sstevel@tonic-gate ef->path = NULL; 185*7c478bd9Sstevel@tonic-gate ef->home = NULL; 186*7c478bd9Sstevel@tonic-gate ef->result.files = NULL; 187*7c478bd9Sstevel@tonic-gate ef->result.nfile = 0; 188*7c478bd9Sstevel@tonic-gate ef->usrnam[0] = '\0'; 189*7c478bd9Sstevel@tonic-gate ef->envnam[0] = '\0'; 190*7c478bd9Sstevel@tonic-gate /* 191*7c478bd9Sstevel@tonic-gate * Allocate a place to record error messages. 192*7c478bd9Sstevel@tonic-gate */ 193*7c478bd9Sstevel@tonic-gate ef->err = _new_ErrMsg(); 194*7c478bd9Sstevel@tonic-gate if(!ef->err) 195*7c478bd9Sstevel@tonic-gate return del_ExpandFile(ef); 196*7c478bd9Sstevel@tonic-gate /* 197*7c478bd9Sstevel@tonic-gate * Allocate a list of string segments for storing filenames. 198*7c478bd9Sstevel@tonic-gate */ 199*7c478bd9Sstevel@tonic-gate ef->sg = _new_StringGroup(_pu_pathname_dim()); 200*7c478bd9Sstevel@tonic-gate if(!ef->sg) 201*7c478bd9Sstevel@tonic-gate return del_ExpandFile(ef); 202*7c478bd9Sstevel@tonic-gate /* 203*7c478bd9Sstevel@tonic-gate * Allocate a freelist for allocating directory cache nodes. 204*7c478bd9Sstevel@tonic-gate */ 205*7c478bd9Sstevel@tonic-gate ef->cache.mem = _new_FreeList(sizeof(DirNode), DIR_CACHE_BLK); 206*7c478bd9Sstevel@tonic-gate if(!ef->cache.mem) 207*7c478bd9Sstevel@tonic-gate return del_ExpandFile(ef); 208*7c478bd9Sstevel@tonic-gate /* 209*7c478bd9Sstevel@tonic-gate * Allocate a pathname buffer. 210*7c478bd9Sstevel@tonic-gate */ 211*7c478bd9Sstevel@tonic-gate ef->path = _new_PathName(); 212*7c478bd9Sstevel@tonic-gate if(!ef->path) 213*7c478bd9Sstevel@tonic-gate return del_ExpandFile(ef); 214*7c478bd9Sstevel@tonic-gate /* 215*7c478bd9Sstevel@tonic-gate * Allocate an object for looking up home-directories. 216*7c478bd9Sstevel@tonic-gate */ 217*7c478bd9Sstevel@tonic-gate ef->home = _new_HomeDir(); 218*7c478bd9Sstevel@tonic-gate if(!ef->home) 219*7c478bd9Sstevel@tonic-gate return del_ExpandFile(ef); 220*7c478bd9Sstevel@tonic-gate /* 221*7c478bd9Sstevel@tonic-gate * Allocate an array for files. This will be extended later if needed. 222*7c478bd9Sstevel@tonic-gate */ 223*7c478bd9Sstevel@tonic-gate ef->files_dim = MATCH_BLK_FACT; 224*7c478bd9Sstevel@tonic-gate ef->result.files = (char **) malloc(sizeof(ef->result.files[0]) * 225*7c478bd9Sstevel@tonic-gate ef->files_dim); 226*7c478bd9Sstevel@tonic-gate if(!ef->result.files) { 227*7c478bd9Sstevel@tonic-gate errno = ENOMEM; 228*7c478bd9Sstevel@tonic-gate return del_ExpandFile(ef); 229*7c478bd9Sstevel@tonic-gate }; 230*7c478bd9Sstevel@tonic-gate return ef; 231*7c478bd9Sstevel@tonic-gate } 232*7c478bd9Sstevel@tonic-gate 233*7c478bd9Sstevel@tonic-gate /*....................................................................... 234*7c478bd9Sstevel@tonic-gate * Delete a ExpandFile object. 235*7c478bd9Sstevel@tonic-gate * 236*7c478bd9Sstevel@tonic-gate * Input: 237*7c478bd9Sstevel@tonic-gate * ef ExpandFile * The object to be deleted. 238*7c478bd9Sstevel@tonic-gate * Output: 239*7c478bd9Sstevel@tonic-gate * return ExpandFile * The deleted object (always NULL). 240*7c478bd9Sstevel@tonic-gate */ 241*7c478bd9Sstevel@tonic-gate ExpandFile *del_ExpandFile(ExpandFile *ef) 242*7c478bd9Sstevel@tonic-gate { 243*7c478bd9Sstevel@tonic-gate if(ef) { 244*7c478bd9Sstevel@tonic-gate DirNode *dnode; 245*7c478bd9Sstevel@tonic-gate /* 246*7c478bd9Sstevel@tonic-gate * Delete the string segments. 247*7c478bd9Sstevel@tonic-gate */ 248*7c478bd9Sstevel@tonic-gate ef->sg = _del_StringGroup(ef->sg); 249*7c478bd9Sstevel@tonic-gate /* 250*7c478bd9Sstevel@tonic-gate * Delete the cached directory readers. 251*7c478bd9Sstevel@tonic-gate */ 252*7c478bd9Sstevel@tonic-gate for(dnode=ef->cache.head; dnode; dnode=dnode->next) 253*7c478bd9Sstevel@tonic-gate dnode->dr = _del_DirReader(dnode->dr); 254*7c478bd9Sstevel@tonic-gate /* 255*7c478bd9Sstevel@tonic-gate * Delete the memory from which the DirNode list was allocated, thus 256*7c478bd9Sstevel@tonic-gate * deleting the list at the same time. 257*7c478bd9Sstevel@tonic-gate */ 258*7c478bd9Sstevel@tonic-gate ef->cache.mem = _del_FreeList(ef->cache.mem, 1); 259*7c478bd9Sstevel@tonic-gate ef->cache.head = ef->cache.tail = ef->cache.next = NULL; 260*7c478bd9Sstevel@tonic-gate /* 261*7c478bd9Sstevel@tonic-gate * Delete the pathname buffer. 262*7c478bd9Sstevel@tonic-gate */ 263*7c478bd9Sstevel@tonic-gate ef->path = _del_PathName(ef->path); 264*7c478bd9Sstevel@tonic-gate /* 265*7c478bd9Sstevel@tonic-gate * Delete the home-directory lookup object. 266*7c478bd9Sstevel@tonic-gate */ 267*7c478bd9Sstevel@tonic-gate ef->home = _del_HomeDir(ef->home); 268*7c478bd9Sstevel@tonic-gate /* 269*7c478bd9Sstevel@tonic-gate * Delete the array of pointers to files. 270*7c478bd9Sstevel@tonic-gate */ 271*7c478bd9Sstevel@tonic-gate if(ef->result.files) { 272*7c478bd9Sstevel@tonic-gate free(ef->result.files); 273*7c478bd9Sstevel@tonic-gate ef->result.files = NULL; 274*7c478bd9Sstevel@tonic-gate }; 275*7c478bd9Sstevel@tonic-gate /* 276*7c478bd9Sstevel@tonic-gate * Delete the error report buffer. 277*7c478bd9Sstevel@tonic-gate */ 278*7c478bd9Sstevel@tonic-gate ef->err = _del_ErrMsg(ef->err); 279*7c478bd9Sstevel@tonic-gate /* 280*7c478bd9Sstevel@tonic-gate * Delete the container. 281*7c478bd9Sstevel@tonic-gate */ 282*7c478bd9Sstevel@tonic-gate free(ef); 283*7c478bd9Sstevel@tonic-gate }; 284*7c478bd9Sstevel@tonic-gate return NULL; 285*7c478bd9Sstevel@tonic-gate } 286*7c478bd9Sstevel@tonic-gate 287*7c478bd9Sstevel@tonic-gate /*....................................................................... 288*7c478bd9Sstevel@tonic-gate * Expand a pathname, converting ~user/ and ~/ patterns at the start 289*7c478bd9Sstevel@tonic-gate * of the pathname to the corresponding home directories, replacing 290*7c478bd9Sstevel@tonic-gate * $envvar with the value of the corresponding environment variable, 291*7c478bd9Sstevel@tonic-gate * and then, if there are any wildcards, matching these against existing 292*7c478bd9Sstevel@tonic-gate * filenames. 293*7c478bd9Sstevel@tonic-gate * 294*7c478bd9Sstevel@tonic-gate * If no errors occur, a container is returned containing the array of 295*7c478bd9Sstevel@tonic-gate * files that resulted from the expansion. If there were no wildcards 296*7c478bd9Sstevel@tonic-gate * in the input pathname, this will contain just the original pathname 297*7c478bd9Sstevel@tonic-gate * after expansion of ~ and $ expressions. If there were any wildcards, 298*7c478bd9Sstevel@tonic-gate * then the array will contain the files that matched them. Note that 299*7c478bd9Sstevel@tonic-gate * if there were any wildcards but no existing files match them, this 300*7c478bd9Sstevel@tonic-gate * is counted as an error and NULL is returned. 301*7c478bd9Sstevel@tonic-gate * 302*7c478bd9Sstevel@tonic-gate * The supported wildcards and their meanings are: 303*7c478bd9Sstevel@tonic-gate * * - Match any sequence of zero or more characters. 304*7c478bd9Sstevel@tonic-gate * ? - Match any single character. 305*7c478bd9Sstevel@tonic-gate * [chars] - Match any single character that appears in 'chars'. 306*7c478bd9Sstevel@tonic-gate * If 'chars' contains an expression of the form a-b, 307*7c478bd9Sstevel@tonic-gate * then any character between a and b, including a and b, 308*7c478bd9Sstevel@tonic-gate * matches. The '-' character looses its special meaning 309*7c478bd9Sstevel@tonic-gate * as a range specifier when it appears at the start 310*7c478bd9Sstevel@tonic-gate * of the sequence of characters. 311*7c478bd9Sstevel@tonic-gate * [^chars] - The same as [chars] except that it matches any single 312*7c478bd9Sstevel@tonic-gate * character that doesn't appear in 'chars'. 313*7c478bd9Sstevel@tonic-gate * 314*7c478bd9Sstevel@tonic-gate * Wildcard expressions are applied to individual filename components. 315*7c478bd9Sstevel@tonic-gate * They don't match across directory separators. A '.' character at 316*7c478bd9Sstevel@tonic-gate * the beginning of a filename component must also be matched 317*7c478bd9Sstevel@tonic-gate * explicitly by a '.' character in the input pathname, since these 318*7c478bd9Sstevel@tonic-gate * are UNIX's hidden files. 319*7c478bd9Sstevel@tonic-gate * 320*7c478bd9Sstevel@tonic-gate * Input: 321*7c478bd9Sstevel@tonic-gate * ef ExpandFile * The pathname expansion resource object. 322*7c478bd9Sstevel@tonic-gate * path char * The path name to be expanded. 323*7c478bd9Sstevel@tonic-gate * pathlen int The length of the suffix of path[] that 324*7c478bd9Sstevel@tonic-gate * constitutes the filename to be expanded, 325*7c478bd9Sstevel@tonic-gate * or -1 to specify that the whole of the 326*7c478bd9Sstevel@tonic-gate * path string should be used. Note that 327*7c478bd9Sstevel@tonic-gate * regardless of the value of this argument, 328*7c478bd9Sstevel@tonic-gate * path[] must contain a '\0' terminated 329*7c478bd9Sstevel@tonic-gate * string, since this function checks that 330*7c478bd9Sstevel@tonic-gate * pathlen isn't mistakenly too long. 331*7c478bd9Sstevel@tonic-gate * Output: 332*7c478bd9Sstevel@tonic-gate * return FileExpansion * A pointer to a container within the given 333*7c478bd9Sstevel@tonic-gate * ExpandFile object. This contains an array 334*7c478bd9Sstevel@tonic-gate * of the pathnames that resulted from expanding 335*7c478bd9Sstevel@tonic-gate * ~ and $ expressions and from matching any 336*7c478bd9Sstevel@tonic-gate * wildcards, sorted into lexical order. 337*7c478bd9Sstevel@tonic-gate * This container and its contents will be 338*7c478bd9Sstevel@tonic-gate * recycled on subsequent calls, so if you need 339*7c478bd9Sstevel@tonic-gate * to keep the results of two successive runs, 340*7c478bd9Sstevel@tonic-gate * you will either have to allocate a private 341*7c478bd9Sstevel@tonic-gate * copy of the array, or use two ExpandFile 342*7c478bd9Sstevel@tonic-gate * objects. 343*7c478bd9Sstevel@tonic-gate * 344*7c478bd9Sstevel@tonic-gate * On error NULL is returned. A description 345*7c478bd9Sstevel@tonic-gate * of the error can be acquired by calling the 346*7c478bd9Sstevel@tonic-gate * ef_last_error() function. 347*7c478bd9Sstevel@tonic-gate */ 348*7c478bd9Sstevel@tonic-gate FileExpansion *ef_expand_file(ExpandFile *ef, const char *path, int pathlen) 349*7c478bd9Sstevel@tonic-gate { 350*7c478bd9Sstevel@tonic-gate DirNode *dnode; /* A directory-reader cache node */ 351*7c478bd9Sstevel@tonic-gate const char *dirname; /* The name of the top level directory of the search */ 352*7c478bd9Sstevel@tonic-gate const char *pptr; /* A pointer into path[] */ 353*7c478bd9Sstevel@tonic-gate int wild; /* True if the path contains any wildcards */ 354*7c478bd9Sstevel@tonic-gate /* 355*7c478bd9Sstevel@tonic-gate * Check the arguments. 356*7c478bd9Sstevel@tonic-gate */ 357*7c478bd9Sstevel@tonic-gate if(!ef || !path) { 358*7c478bd9Sstevel@tonic-gate if(ef) { 359*7c478bd9Sstevel@tonic-gate _err_record_msg(ef->err, "ef_expand_file: NULL path argument", 360*7c478bd9Sstevel@tonic-gate END_ERR_MSG); 361*7c478bd9Sstevel@tonic-gate }; 362*7c478bd9Sstevel@tonic-gate errno = EINVAL; 363*7c478bd9Sstevel@tonic-gate return NULL; 364*7c478bd9Sstevel@tonic-gate }; 365*7c478bd9Sstevel@tonic-gate /* 366*7c478bd9Sstevel@tonic-gate * If the caller specified that the whole of path[] be matched, 367*7c478bd9Sstevel@tonic-gate * work out the corresponding length. 368*7c478bd9Sstevel@tonic-gate */ 369*7c478bd9Sstevel@tonic-gate if(pathlen < 0 || pathlen > strlen(path)) 370*7c478bd9Sstevel@tonic-gate pathlen = strlen(path); 371*7c478bd9Sstevel@tonic-gate /* 372*7c478bd9Sstevel@tonic-gate * Discard previous expansion results. 373*7c478bd9Sstevel@tonic-gate */ 374*7c478bd9Sstevel@tonic-gate ef_clear_files(ef); 375*7c478bd9Sstevel@tonic-gate /* 376*7c478bd9Sstevel@tonic-gate * Preprocess the path, expanding ~/, ~user/ and $envvar references, 377*7c478bd9Sstevel@tonic-gate * using ef->path as a work directory and returning a pointer to 378*7c478bd9Sstevel@tonic-gate * a copy of the resulting pattern in the cache. 379*7c478bd9Sstevel@tonic-gate */ 380*7c478bd9Sstevel@tonic-gate path = ef_expand_special(ef, path, pathlen); 381*7c478bd9Sstevel@tonic-gate if(!path) 382*7c478bd9Sstevel@tonic-gate return NULL; 383*7c478bd9Sstevel@tonic-gate /* 384*7c478bd9Sstevel@tonic-gate * Clear the pathname buffer. 385*7c478bd9Sstevel@tonic-gate */ 386*7c478bd9Sstevel@tonic-gate _pn_clear_path(ef->path); 387*7c478bd9Sstevel@tonic-gate /* 388*7c478bd9Sstevel@tonic-gate * Does the pathname contain any wildcards? 389*7c478bd9Sstevel@tonic-gate */ 390*7c478bd9Sstevel@tonic-gate for(wild=0,pptr=path; !wild && *pptr; pptr++) { 391*7c478bd9Sstevel@tonic-gate switch(*pptr) { 392*7c478bd9Sstevel@tonic-gate case '\\': /* Skip escaped characters */ 393*7c478bd9Sstevel@tonic-gate if(pptr[1]) 394*7c478bd9Sstevel@tonic-gate pptr++; 395*7c478bd9Sstevel@tonic-gate break; 396*7c478bd9Sstevel@tonic-gate case '*': case '?': case '[': /* A wildcard character? */ 397*7c478bd9Sstevel@tonic-gate wild = 1; 398*7c478bd9Sstevel@tonic-gate break; 399*7c478bd9Sstevel@tonic-gate }; 400*7c478bd9Sstevel@tonic-gate }; 401*7c478bd9Sstevel@tonic-gate /* 402*7c478bd9Sstevel@tonic-gate * If there are no wildcards to match, copy the current expanded 403*7c478bd9Sstevel@tonic-gate * path into the output array, removing backslash escapes while doing so. 404*7c478bd9Sstevel@tonic-gate */ 405*7c478bd9Sstevel@tonic-gate if(!wild) { 406*7c478bd9Sstevel@tonic-gate if(ef_record_pathname(ef, path, 1)) 407*7c478bd9Sstevel@tonic-gate return NULL; 408*7c478bd9Sstevel@tonic-gate /* 409*7c478bd9Sstevel@tonic-gate * Does the filename exist? 410*7c478bd9Sstevel@tonic-gate */ 411*7c478bd9Sstevel@tonic-gate ef->result.exists = _pu_file_exists(ef->result.files[0]); 412*7c478bd9Sstevel@tonic-gate /* 413*7c478bd9Sstevel@tonic-gate * Match wildcards against existing files. 414*7c478bd9Sstevel@tonic-gate */ 415*7c478bd9Sstevel@tonic-gate } else { 416*7c478bd9Sstevel@tonic-gate /* 417*7c478bd9Sstevel@tonic-gate * Only existing files that match the pattern will be returned in the 418*7c478bd9Sstevel@tonic-gate * cache. 419*7c478bd9Sstevel@tonic-gate */ 420*7c478bd9Sstevel@tonic-gate ef->result.exists = 1; 421*7c478bd9Sstevel@tonic-gate /* 422*7c478bd9Sstevel@tonic-gate * Treat matching of the root-directory as a special case since it 423*7c478bd9Sstevel@tonic-gate * isn't contained in a directory. 424*7c478bd9Sstevel@tonic-gate */ 425*7c478bd9Sstevel@tonic-gate if(strcmp(path, FS_ROOT_DIR) == 0) { 426*7c478bd9Sstevel@tonic-gate if(ef_record_pathname(ef, FS_ROOT_DIR, 0)) 427*7c478bd9Sstevel@tonic-gate return NULL; 428*7c478bd9Sstevel@tonic-gate } else { 429*7c478bd9Sstevel@tonic-gate /* 430*7c478bd9Sstevel@tonic-gate * What should the top level directory of the search be? 431*7c478bd9Sstevel@tonic-gate */ 432*7c478bd9Sstevel@tonic-gate if(strncmp(path, FS_ROOT_DIR, FS_ROOT_DIR_LEN) == 0) { 433*7c478bd9Sstevel@tonic-gate dirname = FS_ROOT_DIR; 434*7c478bd9Sstevel@tonic-gate if(!_pn_append_to_path(ef->path, FS_ROOT_DIR, -1, 0)) { 435*7c478bd9Sstevel@tonic-gate _err_record_msg(ef->err, "Insufficient memory to record path", 436*7c478bd9Sstevel@tonic-gate END_ERR_MSG); 437*7c478bd9Sstevel@tonic-gate return NULL; 438*7c478bd9Sstevel@tonic-gate }; 439*7c478bd9Sstevel@tonic-gate path += FS_ROOT_DIR_LEN; 440*7c478bd9Sstevel@tonic-gate } else { 441*7c478bd9Sstevel@tonic-gate dirname = FS_PWD; 442*7c478bd9Sstevel@tonic-gate }; 443*7c478bd9Sstevel@tonic-gate /* 444*7c478bd9Sstevel@tonic-gate * Open the top-level directory of the search. 445*7c478bd9Sstevel@tonic-gate */ 446*7c478bd9Sstevel@tonic-gate dnode = ef_open_dir(ef, dirname); 447*7c478bd9Sstevel@tonic-gate if(!dnode) 448*7c478bd9Sstevel@tonic-gate return NULL; 449*7c478bd9Sstevel@tonic-gate /* 450*7c478bd9Sstevel@tonic-gate * Recursively match successive directory components of the path. 451*7c478bd9Sstevel@tonic-gate */ 452*7c478bd9Sstevel@tonic-gate if(ef_match_relative_pathname(ef, dnode->dr, path, 0)) { 453*7c478bd9Sstevel@tonic-gate dnode = ef_close_dir(ef, dnode); 454*7c478bd9Sstevel@tonic-gate return NULL; 455*7c478bd9Sstevel@tonic-gate }; 456*7c478bd9Sstevel@tonic-gate /* 457*7c478bd9Sstevel@tonic-gate * Cleanup. 458*7c478bd9Sstevel@tonic-gate */ 459*7c478bd9Sstevel@tonic-gate dnode = ef_close_dir(ef, dnode); 460*7c478bd9Sstevel@tonic-gate }; 461*7c478bd9Sstevel@tonic-gate /* 462*7c478bd9Sstevel@tonic-gate * No files matched? 463*7c478bd9Sstevel@tonic-gate */ 464*7c478bd9Sstevel@tonic-gate if(ef->result.nfile < 1) { 465*7c478bd9Sstevel@tonic-gate _err_record_msg(ef->err, "No files match", END_ERR_MSG); 466*7c478bd9Sstevel@tonic-gate return NULL; 467*7c478bd9Sstevel@tonic-gate }; 468*7c478bd9Sstevel@tonic-gate /* 469*7c478bd9Sstevel@tonic-gate * Sort the pathnames that matched. 470*7c478bd9Sstevel@tonic-gate */ 471*7c478bd9Sstevel@tonic-gate qsort(ef->result.files, ef->result.nfile, sizeof(ef->result.files[0]), 472*7c478bd9Sstevel@tonic-gate ef_cmp_strings); 473*7c478bd9Sstevel@tonic-gate }; 474*7c478bd9Sstevel@tonic-gate /* 475*7c478bd9Sstevel@tonic-gate * Return the result container. 476*7c478bd9Sstevel@tonic-gate */ 477*7c478bd9Sstevel@tonic-gate return &ef->result; 478*7c478bd9Sstevel@tonic-gate } 479*7c478bd9Sstevel@tonic-gate 480*7c478bd9Sstevel@tonic-gate /*....................................................................... 481*7c478bd9Sstevel@tonic-gate * Attempt to recursively match the given pattern with the contents of 482*7c478bd9Sstevel@tonic-gate * the current directory, descending sub-directories as needed. 483*7c478bd9Sstevel@tonic-gate * 484*7c478bd9Sstevel@tonic-gate * Input: 485*7c478bd9Sstevel@tonic-gate * ef ExpandFile * The pathname expansion resource object. 486*7c478bd9Sstevel@tonic-gate * dr DirReader * The directory reader object of the directory 487*7c478bd9Sstevel@tonic-gate * to be searched. 488*7c478bd9Sstevel@tonic-gate * pattern const char * The pattern to match with files in the current 489*7c478bd9Sstevel@tonic-gate * directory. 490*7c478bd9Sstevel@tonic-gate * separate int When appending a filename from the specified 491*7c478bd9Sstevel@tonic-gate * directory to ef->pathname, insert a directory 492*7c478bd9Sstevel@tonic-gate * separator between the existing pathname and 493*7c478bd9Sstevel@tonic-gate * the filename, unless separate is zero. 494*7c478bd9Sstevel@tonic-gate * Output: 495*7c478bd9Sstevel@tonic-gate * return int 0 - OK. 496*7c478bd9Sstevel@tonic-gate * 1 - Error. 497*7c478bd9Sstevel@tonic-gate */ 498*7c478bd9Sstevel@tonic-gate static int ef_match_relative_pathname(ExpandFile *ef, DirReader *dr, 499*7c478bd9Sstevel@tonic-gate const char *pattern, int separate) 500*7c478bd9Sstevel@tonic-gate { 501*7c478bd9Sstevel@tonic-gate const char *nextp; /* The pointer to the character that follows the part */ 502*7c478bd9Sstevel@tonic-gate /* of the pattern that is to be matched with files */ 503*7c478bd9Sstevel@tonic-gate /* in the current directory. */ 504*7c478bd9Sstevel@tonic-gate char *file; /* The name of the file being matched */ 505*7c478bd9Sstevel@tonic-gate int pathlen; /* The length of ef->pathname[] on entry to this */ 506*7c478bd9Sstevel@tonic-gate /* function */ 507*7c478bd9Sstevel@tonic-gate /* 508*7c478bd9Sstevel@tonic-gate * Record the current length of the pathname string recorded in 509*7c478bd9Sstevel@tonic-gate * ef->pathname[]. 510*7c478bd9Sstevel@tonic-gate */ 511*7c478bd9Sstevel@tonic-gate pathlen = strlen(ef->path->name); 512*7c478bd9Sstevel@tonic-gate /* 513*7c478bd9Sstevel@tonic-gate * Get a pointer to the character that follows the end of the part of 514*7c478bd9Sstevel@tonic-gate * the pattern that should be matched to files within the current directory. 515*7c478bd9Sstevel@tonic-gate * This will either point to a directory separator, or to the '\0' terminator 516*7c478bd9Sstevel@tonic-gate * of the pattern string. 517*7c478bd9Sstevel@tonic-gate */ 518*7c478bd9Sstevel@tonic-gate for(nextp=pattern; *nextp && strncmp(nextp, FS_DIR_SEP, FS_DIR_SEP_LEN) != 0; 519*7c478bd9Sstevel@tonic-gate nextp++) 520*7c478bd9Sstevel@tonic-gate ; 521*7c478bd9Sstevel@tonic-gate /* 522*7c478bd9Sstevel@tonic-gate * Read each file from the directory, attempting to match it to the 523*7c478bd9Sstevel@tonic-gate * current pattern. 524*7c478bd9Sstevel@tonic-gate */ 525*7c478bd9Sstevel@tonic-gate while((file=_dr_next_file(dr)) != NULL) { 526*7c478bd9Sstevel@tonic-gate /* 527*7c478bd9Sstevel@tonic-gate * Does the latest file match the pattern up to nextp? 528*7c478bd9Sstevel@tonic-gate */ 529*7c478bd9Sstevel@tonic-gate if(ef_string_matches_pattern(file, pattern, file[0]=='.', nextp)) { 530*7c478bd9Sstevel@tonic-gate /* 531*7c478bd9Sstevel@tonic-gate * Append the new directory entry to the current matching pathname. 532*7c478bd9Sstevel@tonic-gate */ 533*7c478bd9Sstevel@tonic-gate if((separate && _pn_append_to_path(ef->path, FS_DIR_SEP, -1, 0)==NULL) || 534*7c478bd9Sstevel@tonic-gate _pn_append_to_path(ef->path, file, -1, 0)==NULL) { 535*7c478bd9Sstevel@tonic-gate _err_record_msg(ef->err, "Insufficient memory to record path", 536*7c478bd9Sstevel@tonic-gate END_ERR_MSG); 537*7c478bd9Sstevel@tonic-gate return 1; 538*7c478bd9Sstevel@tonic-gate }; 539*7c478bd9Sstevel@tonic-gate /* 540*7c478bd9Sstevel@tonic-gate * If we have reached the end of the pattern, record the accumulated 541*7c478bd9Sstevel@tonic-gate * pathname in the list of matching files. 542*7c478bd9Sstevel@tonic-gate */ 543*7c478bd9Sstevel@tonic-gate if(*nextp == '\0') { 544*7c478bd9Sstevel@tonic-gate if(ef_record_pathname(ef, ef->path->name, 0)) 545*7c478bd9Sstevel@tonic-gate return 1; 546*7c478bd9Sstevel@tonic-gate /* 547*7c478bd9Sstevel@tonic-gate * If the matching directory entry is a subdirectory, and the 548*7c478bd9Sstevel@tonic-gate * next character of the pattern is a directory separator, 549*7c478bd9Sstevel@tonic-gate * recursively call the current function to scan the sub-directory 550*7c478bd9Sstevel@tonic-gate * for matches. 551*7c478bd9Sstevel@tonic-gate */ 552*7c478bd9Sstevel@tonic-gate } else if(_pu_path_is_dir(ef->path->name) && 553*7c478bd9Sstevel@tonic-gate strncmp(nextp, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0) { 554*7c478bd9Sstevel@tonic-gate /* 555*7c478bd9Sstevel@tonic-gate * If the pattern finishes with the directory separator, then 556*7c478bd9Sstevel@tonic-gate * record the pathame as matching. 557*7c478bd9Sstevel@tonic-gate */ 558*7c478bd9Sstevel@tonic-gate if(nextp[FS_DIR_SEP_LEN] == '\0') { 559*7c478bd9Sstevel@tonic-gate if(ef_record_pathname(ef, ef->path->name, 0)) 560*7c478bd9Sstevel@tonic-gate return 1; 561*7c478bd9Sstevel@tonic-gate /* 562*7c478bd9Sstevel@tonic-gate * Match files within the directory. 563*7c478bd9Sstevel@tonic-gate */ 564*7c478bd9Sstevel@tonic-gate } else { 565*7c478bd9Sstevel@tonic-gate DirNode *subdnode = ef_open_dir(ef, ef->path->name); 566*7c478bd9Sstevel@tonic-gate if(subdnode) { 567*7c478bd9Sstevel@tonic-gate if(ef_match_relative_pathname(ef, subdnode->dr, 568*7c478bd9Sstevel@tonic-gate nextp+FS_DIR_SEP_LEN, 1)) { 569*7c478bd9Sstevel@tonic-gate subdnode = ef_close_dir(ef, subdnode); 570*7c478bd9Sstevel@tonic-gate return 1; 571*7c478bd9Sstevel@tonic-gate }; 572*7c478bd9Sstevel@tonic-gate subdnode = ef_close_dir(ef, subdnode); 573*7c478bd9Sstevel@tonic-gate }; 574*7c478bd9Sstevel@tonic-gate }; 575*7c478bd9Sstevel@tonic-gate }; 576*7c478bd9Sstevel@tonic-gate /* 577*7c478bd9Sstevel@tonic-gate * Remove the latest filename from the pathname string, so that 578*7c478bd9Sstevel@tonic-gate * another matching file can be appended. 579*7c478bd9Sstevel@tonic-gate */ 580*7c478bd9Sstevel@tonic-gate ef->path->name[pathlen] = '\0'; 581*7c478bd9Sstevel@tonic-gate }; 582*7c478bd9Sstevel@tonic-gate }; 583*7c478bd9Sstevel@tonic-gate return 0; 584*7c478bd9Sstevel@tonic-gate } 585*7c478bd9Sstevel@tonic-gate 586*7c478bd9Sstevel@tonic-gate /*....................................................................... 587*7c478bd9Sstevel@tonic-gate * Record a new matching filename. 588*7c478bd9Sstevel@tonic-gate * 589*7c478bd9Sstevel@tonic-gate * Input: 590*7c478bd9Sstevel@tonic-gate * ef ExpandFile * The filename-match resource object. 591*7c478bd9Sstevel@tonic-gate * pathname const char * The pathname to record. 592*7c478bd9Sstevel@tonic-gate * remove_escapes int If true, remove backslash escapes in the 593*7c478bd9Sstevel@tonic-gate * recorded copy of the pathname. 594*7c478bd9Sstevel@tonic-gate * Output: 595*7c478bd9Sstevel@tonic-gate * return int 0 - OK. 596*7c478bd9Sstevel@tonic-gate * 1 - Error (ef->err will contain a 597*7c478bd9Sstevel@tonic-gate * description of the error). 598*7c478bd9Sstevel@tonic-gate */ 599*7c478bd9Sstevel@tonic-gate static int ef_record_pathname(ExpandFile *ef, const char *pathname, 600*7c478bd9Sstevel@tonic-gate int remove_escapes) 601*7c478bd9Sstevel@tonic-gate { 602*7c478bd9Sstevel@tonic-gate char *copy; /* The recorded copy of pathname[] */ 603*7c478bd9Sstevel@tonic-gate /* 604*7c478bd9Sstevel@tonic-gate * Attempt to make a copy of the pathname in the cache. 605*7c478bd9Sstevel@tonic-gate */ 606*7c478bd9Sstevel@tonic-gate copy = ef_cache_pathname(ef, pathname, remove_escapes); 607*7c478bd9Sstevel@tonic-gate if(!copy) 608*7c478bd9Sstevel@tonic-gate return 1; 609*7c478bd9Sstevel@tonic-gate /* 610*7c478bd9Sstevel@tonic-gate * If there isn't room to record a pointer to the recorded pathname in the 611*7c478bd9Sstevel@tonic-gate * array of files, attempt to extend the array. 612*7c478bd9Sstevel@tonic-gate */ 613*7c478bd9Sstevel@tonic-gate if(ef->result.nfile + 1 > ef->files_dim) { 614*7c478bd9Sstevel@tonic-gate int files_dim = ef->files_dim + MATCH_BLK_FACT; 615*7c478bd9Sstevel@tonic-gate char **files = (char **) realloc(ef->result.files, 616*7c478bd9Sstevel@tonic-gate files_dim * sizeof(files[0])); 617*7c478bd9Sstevel@tonic-gate if(!files) { 618*7c478bd9Sstevel@tonic-gate _err_record_msg(ef->err, 619*7c478bd9Sstevel@tonic-gate "Insufficient memory to record all of the matching filenames", 620*7c478bd9Sstevel@tonic-gate END_ERR_MSG); 621*7c478bd9Sstevel@tonic-gate errno = ENOMEM; 622*7c478bd9Sstevel@tonic-gate return 1; 623*7c478bd9Sstevel@tonic-gate }; 624*7c478bd9Sstevel@tonic-gate ef->result.files = files; 625*7c478bd9Sstevel@tonic-gate ef->files_dim = files_dim; 626*7c478bd9Sstevel@tonic-gate }; 627*7c478bd9Sstevel@tonic-gate /* 628*7c478bd9Sstevel@tonic-gate * Record a pointer to the new match. 629*7c478bd9Sstevel@tonic-gate */ 630*7c478bd9Sstevel@tonic-gate ef->result.files[ef->result.nfile++] = copy; 631*7c478bd9Sstevel@tonic-gate return 0; 632*7c478bd9Sstevel@tonic-gate } 633*7c478bd9Sstevel@tonic-gate 634*7c478bd9Sstevel@tonic-gate /*....................................................................... 635*7c478bd9Sstevel@tonic-gate * Record a pathname in the cache. 636*7c478bd9Sstevel@tonic-gate * 637*7c478bd9Sstevel@tonic-gate * Input: 638*7c478bd9Sstevel@tonic-gate * ef ExpandFile * The filename-match resource object. 639*7c478bd9Sstevel@tonic-gate * pathname char * The pathname to record. 640*7c478bd9Sstevel@tonic-gate * remove_escapes int If true, remove backslash escapes in the 641*7c478bd9Sstevel@tonic-gate * copy of the pathname. 642*7c478bd9Sstevel@tonic-gate * Output: 643*7c478bd9Sstevel@tonic-gate * return char * The pointer to the copy of the pathname. 644*7c478bd9Sstevel@tonic-gate * On error NULL is returned and a description 645*7c478bd9Sstevel@tonic-gate * of the error is left in ef->err. 646*7c478bd9Sstevel@tonic-gate */ 647*7c478bd9Sstevel@tonic-gate static char *ef_cache_pathname(ExpandFile *ef, const char *pathname, 648*7c478bd9Sstevel@tonic-gate int remove_escapes) 649*7c478bd9Sstevel@tonic-gate { 650*7c478bd9Sstevel@tonic-gate char *copy = _sg_store_string(ef->sg, pathname, remove_escapes); 651*7c478bd9Sstevel@tonic-gate if(!copy) 652*7c478bd9Sstevel@tonic-gate _err_record_msg(ef->err, "Insufficient memory to store pathname", 653*7c478bd9Sstevel@tonic-gate END_ERR_MSG); 654*7c478bd9Sstevel@tonic-gate return copy; 655*7c478bd9Sstevel@tonic-gate } 656*7c478bd9Sstevel@tonic-gate 657*7c478bd9Sstevel@tonic-gate /*....................................................................... 658*7c478bd9Sstevel@tonic-gate * Clear the results of the previous expansion operation, ready for the 659*7c478bd9Sstevel@tonic-gate * next. 660*7c478bd9Sstevel@tonic-gate * 661*7c478bd9Sstevel@tonic-gate * Input: 662*7c478bd9Sstevel@tonic-gate * ef ExpandFile * The pathname expansion resource object. 663*7c478bd9Sstevel@tonic-gate */ 664*7c478bd9Sstevel@tonic-gate static void ef_clear_files(ExpandFile *ef) 665*7c478bd9Sstevel@tonic-gate { 666*7c478bd9Sstevel@tonic-gate _clr_StringGroup(ef->sg); 667*7c478bd9Sstevel@tonic-gate _pn_clear_path(ef->path); 668*7c478bd9Sstevel@tonic-gate ef->result.exists = 0; 669*7c478bd9Sstevel@tonic-gate ef->result.nfile = 0; 670*7c478bd9Sstevel@tonic-gate _err_clear_msg(ef->err); 671*7c478bd9Sstevel@tonic-gate return; 672*7c478bd9Sstevel@tonic-gate } 673*7c478bd9Sstevel@tonic-gate 674*7c478bd9Sstevel@tonic-gate /*....................................................................... 675*7c478bd9Sstevel@tonic-gate * Get a new directory reader object from the cache. 676*7c478bd9Sstevel@tonic-gate * 677*7c478bd9Sstevel@tonic-gate * Input: 678*7c478bd9Sstevel@tonic-gate * ef ExpandFile * The pathname expansion resource object. 679*7c478bd9Sstevel@tonic-gate * pathname const char * The pathname of the directory. 680*7c478bd9Sstevel@tonic-gate * Output: 681*7c478bd9Sstevel@tonic-gate * return DirNode * The cache entry of the new directory reader, 682*7c478bd9Sstevel@tonic-gate * or NULL on error. On error, ef->err will 683*7c478bd9Sstevel@tonic-gate * contain a description of the error. 684*7c478bd9Sstevel@tonic-gate */ 685*7c478bd9Sstevel@tonic-gate static DirNode *ef_open_dir(ExpandFile *ef, const char *pathname) 686*7c478bd9Sstevel@tonic-gate { 687*7c478bd9Sstevel@tonic-gate char *errmsg = NULL; /* An error message from a called function */ 688*7c478bd9Sstevel@tonic-gate DirNode *node; /* The cache node used */ 689*7c478bd9Sstevel@tonic-gate /* 690*7c478bd9Sstevel@tonic-gate * Get the directory reader cache. 691*7c478bd9Sstevel@tonic-gate */ 692*7c478bd9Sstevel@tonic-gate DirCache *cache = &ef->cache; 693*7c478bd9Sstevel@tonic-gate /* 694*7c478bd9Sstevel@tonic-gate * Extend the cache if there are no free cache nodes. 695*7c478bd9Sstevel@tonic-gate */ 696*7c478bd9Sstevel@tonic-gate if(!cache->next) { 697*7c478bd9Sstevel@tonic-gate node = (DirNode *) _new_FreeListNode(cache->mem); 698*7c478bd9Sstevel@tonic-gate if(!node) { 699*7c478bd9Sstevel@tonic-gate _err_record_msg(ef->err, "Insufficient memory to open a new directory", 700*7c478bd9Sstevel@tonic-gate END_ERR_MSG); 701*7c478bd9Sstevel@tonic-gate return NULL; 702*7c478bd9Sstevel@tonic-gate }; 703*7c478bd9Sstevel@tonic-gate /* 704*7c478bd9Sstevel@tonic-gate * Initialize the cache node. 705*7c478bd9Sstevel@tonic-gate */ 706*7c478bd9Sstevel@tonic-gate node->next = NULL; 707*7c478bd9Sstevel@tonic-gate node->prev = NULL; 708*7c478bd9Sstevel@tonic-gate node->dr = NULL; 709*7c478bd9Sstevel@tonic-gate /* 710*7c478bd9Sstevel@tonic-gate * Allocate a directory reader object. 711*7c478bd9Sstevel@tonic-gate */ 712*7c478bd9Sstevel@tonic-gate node->dr = _new_DirReader(); 713*7c478bd9Sstevel@tonic-gate if(!node->dr) { 714*7c478bd9Sstevel@tonic-gate _err_record_msg(ef->err, "Insufficient memory to open a new directory", 715*7c478bd9Sstevel@tonic-gate END_ERR_MSG); 716*7c478bd9Sstevel@tonic-gate node = (DirNode *) _del_FreeListNode(cache->mem, node); 717*7c478bd9Sstevel@tonic-gate return NULL; 718*7c478bd9Sstevel@tonic-gate }; 719*7c478bd9Sstevel@tonic-gate /* 720*7c478bd9Sstevel@tonic-gate * Append the node to the cache list. 721*7c478bd9Sstevel@tonic-gate */ 722*7c478bd9Sstevel@tonic-gate node->prev = cache->tail; 723*7c478bd9Sstevel@tonic-gate if(cache->tail) 724*7c478bd9Sstevel@tonic-gate cache->tail->next = node; 725*7c478bd9Sstevel@tonic-gate else 726*7c478bd9Sstevel@tonic-gate cache->head = node; 727*7c478bd9Sstevel@tonic-gate cache->next = cache->tail = node; 728*7c478bd9Sstevel@tonic-gate }; 729*7c478bd9Sstevel@tonic-gate /* 730*7c478bd9Sstevel@tonic-gate * Get the first unused node, but don't remove it from the list yet. 731*7c478bd9Sstevel@tonic-gate */ 732*7c478bd9Sstevel@tonic-gate node = cache->next; 733*7c478bd9Sstevel@tonic-gate /* 734*7c478bd9Sstevel@tonic-gate * Attempt to open the specified directory. 735*7c478bd9Sstevel@tonic-gate */ 736*7c478bd9Sstevel@tonic-gate if(_dr_open_dir(node->dr, pathname, &errmsg)) { 737*7c478bd9Sstevel@tonic-gate _err_record_msg(ef->err, errmsg, END_ERR_MSG); 738*7c478bd9Sstevel@tonic-gate return NULL; 739*7c478bd9Sstevel@tonic-gate }; 740*7c478bd9Sstevel@tonic-gate /* 741*7c478bd9Sstevel@tonic-gate * Now that we have successfully opened the specified directory, 742*7c478bd9Sstevel@tonic-gate * remove the cache node from the list, and relink the list around it. 743*7c478bd9Sstevel@tonic-gate */ 744*7c478bd9Sstevel@tonic-gate cache->next = node->next; 745*7c478bd9Sstevel@tonic-gate if(node->prev) 746*7c478bd9Sstevel@tonic-gate node->prev->next = node->next; 747*7c478bd9Sstevel@tonic-gate else 748*7c478bd9Sstevel@tonic-gate cache->head = node->next; 749*7c478bd9Sstevel@tonic-gate if(node->next) 750*7c478bd9Sstevel@tonic-gate node->next->prev = node->prev; 751*7c478bd9Sstevel@tonic-gate else 752*7c478bd9Sstevel@tonic-gate cache->tail = node->prev; 753*7c478bd9Sstevel@tonic-gate node->next = node->prev = NULL; 754*7c478bd9Sstevel@tonic-gate /* 755*7c478bd9Sstevel@tonic-gate * Return the successfully initialized cache node to the caller. 756*7c478bd9Sstevel@tonic-gate */ 757*7c478bd9Sstevel@tonic-gate return node; 758*7c478bd9Sstevel@tonic-gate } 759*7c478bd9Sstevel@tonic-gate 760*7c478bd9Sstevel@tonic-gate /*....................................................................... 761*7c478bd9Sstevel@tonic-gate * Return a directory reader object to the cache, after first closing 762*7c478bd9Sstevel@tonic-gate * the directory that it was managing. 763*7c478bd9Sstevel@tonic-gate * 764*7c478bd9Sstevel@tonic-gate * Input: 765*7c478bd9Sstevel@tonic-gate * ef ExpandFile * The pathname expansion resource object. 766*7c478bd9Sstevel@tonic-gate * node DirNode * The cache entry of the directory reader, as returned 767*7c478bd9Sstevel@tonic-gate * by ef_open_dir(). 768*7c478bd9Sstevel@tonic-gate * Output: 769*7c478bd9Sstevel@tonic-gate * return DirNode * The deleted DirNode (ie. allways NULL). 770*7c478bd9Sstevel@tonic-gate */ 771*7c478bd9Sstevel@tonic-gate static DirNode *ef_close_dir(ExpandFile *ef, DirNode *node) 772*7c478bd9Sstevel@tonic-gate { 773*7c478bd9Sstevel@tonic-gate /* 774*7c478bd9Sstevel@tonic-gate * Get the directory reader cache. 775*7c478bd9Sstevel@tonic-gate */ 776*7c478bd9Sstevel@tonic-gate DirCache *cache = &ef->cache; 777*7c478bd9Sstevel@tonic-gate /* 778*7c478bd9Sstevel@tonic-gate * Close the directory. 779*7c478bd9Sstevel@tonic-gate */ 780*7c478bd9Sstevel@tonic-gate _dr_close_dir(node->dr); 781*7c478bd9Sstevel@tonic-gate /* 782*7c478bd9Sstevel@tonic-gate * Return the node to the tail of the cache list. 783*7c478bd9Sstevel@tonic-gate */ 784*7c478bd9Sstevel@tonic-gate node->next = NULL; 785*7c478bd9Sstevel@tonic-gate node->prev = cache->tail; 786*7c478bd9Sstevel@tonic-gate if(cache->tail) 787*7c478bd9Sstevel@tonic-gate cache->tail->next = node; 788*7c478bd9Sstevel@tonic-gate else 789*7c478bd9Sstevel@tonic-gate cache->head = cache->tail = node; 790*7c478bd9Sstevel@tonic-gate if(!cache->next) 791*7c478bd9Sstevel@tonic-gate cache->next = node; 792*7c478bd9Sstevel@tonic-gate return NULL; 793*7c478bd9Sstevel@tonic-gate } 794*7c478bd9Sstevel@tonic-gate 795*7c478bd9Sstevel@tonic-gate /*....................................................................... 796*7c478bd9Sstevel@tonic-gate * Return non-zero if the specified file name matches a given glob 797*7c478bd9Sstevel@tonic-gate * pattern. 798*7c478bd9Sstevel@tonic-gate * 799*7c478bd9Sstevel@tonic-gate * Input: 800*7c478bd9Sstevel@tonic-gate * file const char * The file-name component to be matched to the pattern. 801*7c478bd9Sstevel@tonic-gate * pattern const char * The start of the pattern to match against file[]. 802*7c478bd9Sstevel@tonic-gate * xplicit int If non-zero, the first character must be matched 803*7c478bd9Sstevel@tonic-gate * explicitly (ie. not with a wildcard). 804*7c478bd9Sstevel@tonic-gate * nextp const char * The pointer to the the character following the 805*7c478bd9Sstevel@tonic-gate * end of the pattern in pattern[]. 806*7c478bd9Sstevel@tonic-gate * Output: 807*7c478bd9Sstevel@tonic-gate * return int 0 - Doesn't match. 808*7c478bd9Sstevel@tonic-gate * 1 - The file-name string matches the pattern. 809*7c478bd9Sstevel@tonic-gate */ 810*7c478bd9Sstevel@tonic-gate static int ef_string_matches_pattern(const char *file, const char *pattern, 811*7c478bd9Sstevel@tonic-gate int xplicit, const char *nextp) 812*7c478bd9Sstevel@tonic-gate { 813*7c478bd9Sstevel@tonic-gate const char *pptr = pattern; /* The pointer used to scan the pattern */ 814*7c478bd9Sstevel@tonic-gate const char *fptr = file; /* The pointer used to scan the filename string */ 815*7c478bd9Sstevel@tonic-gate /* 816*7c478bd9Sstevel@tonic-gate * Match each character of the pattern in turn. 817*7c478bd9Sstevel@tonic-gate */ 818*7c478bd9Sstevel@tonic-gate while(pptr < nextp) { 819*7c478bd9Sstevel@tonic-gate /* 820*7c478bd9Sstevel@tonic-gate * Handle the next character of the pattern. 821*7c478bd9Sstevel@tonic-gate */ 822*7c478bd9Sstevel@tonic-gate switch(*pptr) { 823*7c478bd9Sstevel@tonic-gate /* 824*7c478bd9Sstevel@tonic-gate * A match zero-or-more characters wildcard operator. 825*7c478bd9Sstevel@tonic-gate */ 826*7c478bd9Sstevel@tonic-gate case '*': 827*7c478bd9Sstevel@tonic-gate /* 828*7c478bd9Sstevel@tonic-gate * Skip the '*' character in the pattern. 829*7c478bd9Sstevel@tonic-gate */ 830*7c478bd9Sstevel@tonic-gate pptr++; 831*7c478bd9Sstevel@tonic-gate /* 832*7c478bd9Sstevel@tonic-gate * If wildcards aren't allowed, the pattern doesn't match. 833*7c478bd9Sstevel@tonic-gate */ 834*7c478bd9Sstevel@tonic-gate if(xplicit) 835*7c478bd9Sstevel@tonic-gate return 0; 836*7c478bd9Sstevel@tonic-gate /* 837*7c478bd9Sstevel@tonic-gate * If the pattern ends with a the '*' wildcard, then the 838*7c478bd9Sstevel@tonic-gate * rest of the filename matches this. 839*7c478bd9Sstevel@tonic-gate */ 840*7c478bd9Sstevel@tonic-gate if(pptr >= nextp) 841*7c478bd9Sstevel@tonic-gate return 1; 842*7c478bd9Sstevel@tonic-gate /* 843*7c478bd9Sstevel@tonic-gate * Using the wildcard to match successively longer sections of 844*7c478bd9Sstevel@tonic-gate * the remaining characters of the filename, attempt to match 845*7c478bd9Sstevel@tonic-gate * the tail of the filename against the tail of the pattern. 846*7c478bd9Sstevel@tonic-gate */ 847*7c478bd9Sstevel@tonic-gate for( ; *fptr; fptr++) { 848*7c478bd9Sstevel@tonic-gate if(ef_string_matches_pattern(fptr, pptr, 0, nextp)) 849*7c478bd9Sstevel@tonic-gate return 1; 850*7c478bd9Sstevel@tonic-gate }; 851*7c478bd9Sstevel@tonic-gate return 0; /* The pattern following the '*' didn't match */ 852*7c478bd9Sstevel@tonic-gate break; 853*7c478bd9Sstevel@tonic-gate /* 854*7c478bd9Sstevel@tonic-gate * A match-one-character wildcard operator. 855*7c478bd9Sstevel@tonic-gate */ 856*7c478bd9Sstevel@tonic-gate case '?': 857*7c478bd9Sstevel@tonic-gate /* 858*7c478bd9Sstevel@tonic-gate * If there is a character to be matched, skip it and advance the 859*7c478bd9Sstevel@tonic-gate * pattern pointer. 860*7c478bd9Sstevel@tonic-gate */ 861*7c478bd9Sstevel@tonic-gate if(!xplicit && *fptr) { 862*7c478bd9Sstevel@tonic-gate fptr++; 863*7c478bd9Sstevel@tonic-gate pptr++; 864*7c478bd9Sstevel@tonic-gate /* 865*7c478bd9Sstevel@tonic-gate * If we hit the end of the filename string, there is no character 866*7c478bd9Sstevel@tonic-gate * matching the operator, so the string doesn't match. 867*7c478bd9Sstevel@tonic-gate */ 868*7c478bd9Sstevel@tonic-gate } else { 869*7c478bd9Sstevel@tonic-gate return 0; 870*7c478bd9Sstevel@tonic-gate }; 871*7c478bd9Sstevel@tonic-gate break; 872*7c478bd9Sstevel@tonic-gate /* 873*7c478bd9Sstevel@tonic-gate * A character range operator, with the character ranges enclosed 874*7c478bd9Sstevel@tonic-gate * in matching square brackets. 875*7c478bd9Sstevel@tonic-gate */ 876*7c478bd9Sstevel@tonic-gate case '[': 877*7c478bd9Sstevel@tonic-gate if(xplicit || !ef_matches_range(*fptr++, ++pptr, &pptr)) 878*7c478bd9Sstevel@tonic-gate return 0; 879*7c478bd9Sstevel@tonic-gate break; 880*7c478bd9Sstevel@tonic-gate /* 881*7c478bd9Sstevel@tonic-gate * A backslash in the pattern prevents the following character as 882*7c478bd9Sstevel@tonic-gate * being seen as a special character. 883*7c478bd9Sstevel@tonic-gate */ 884*7c478bd9Sstevel@tonic-gate case '\\': 885*7c478bd9Sstevel@tonic-gate pptr++; 886*7c478bd9Sstevel@tonic-gate /* Note fallthrough to default */ 887*7c478bd9Sstevel@tonic-gate /* 888*7c478bd9Sstevel@tonic-gate * A normal character to be matched explicitly. 889*7c478bd9Sstevel@tonic-gate */ 890*7c478bd9Sstevel@tonic-gate default: 891*7c478bd9Sstevel@tonic-gate if(*fptr == *pptr) { 892*7c478bd9Sstevel@tonic-gate fptr++; 893*7c478bd9Sstevel@tonic-gate pptr++; 894*7c478bd9Sstevel@tonic-gate } else { 895*7c478bd9Sstevel@tonic-gate return 0; 896*7c478bd9Sstevel@tonic-gate }; 897*7c478bd9Sstevel@tonic-gate break; 898*7c478bd9Sstevel@tonic-gate }; 899*7c478bd9Sstevel@tonic-gate /* 900*7c478bd9Sstevel@tonic-gate * After passing the first character, turn off the explicit match 901*7c478bd9Sstevel@tonic-gate * requirement. 902*7c478bd9Sstevel@tonic-gate */ 903*7c478bd9Sstevel@tonic-gate xplicit = 0; 904*7c478bd9Sstevel@tonic-gate }; 905*7c478bd9Sstevel@tonic-gate /* 906*7c478bd9Sstevel@tonic-gate * To get here the pattern must have been exhausted. If the filename 907*7c478bd9Sstevel@tonic-gate * string matched, then the filename string must also have been 908*7c478bd9Sstevel@tonic-gate * exhausted. 909*7c478bd9Sstevel@tonic-gate */ 910*7c478bd9Sstevel@tonic-gate return *fptr == '\0'; 911*7c478bd9Sstevel@tonic-gate } 912*7c478bd9Sstevel@tonic-gate 913*7c478bd9Sstevel@tonic-gate /*....................................................................... 914*7c478bd9Sstevel@tonic-gate * Match a character range expression terminated by an unescaped close 915*7c478bd9Sstevel@tonic-gate * square bracket. 916*7c478bd9Sstevel@tonic-gate * 917*7c478bd9Sstevel@tonic-gate * Input: 918*7c478bd9Sstevel@tonic-gate * c int The character to be matched with the range 919*7c478bd9Sstevel@tonic-gate * pattern. 920*7c478bd9Sstevel@tonic-gate * pattern const char * The range pattern to be matched (ie. after the 921*7c478bd9Sstevel@tonic-gate * initiating '[' character). 922*7c478bd9Sstevel@tonic-gate * endp const char ** On output a pointer to the character following the 923*7c478bd9Sstevel@tonic-gate * range expression will be assigned to *endp. 924*7c478bd9Sstevel@tonic-gate * Output: 925*7c478bd9Sstevel@tonic-gate * return int 0 - Doesn't match. 926*7c478bd9Sstevel@tonic-gate * 1 - The character matched. 927*7c478bd9Sstevel@tonic-gate */ 928*7c478bd9Sstevel@tonic-gate static int ef_matches_range(int c, const char *pattern, const char **endp) 929*7c478bd9Sstevel@tonic-gate { 930*7c478bd9Sstevel@tonic-gate const char *pptr = pattern; /* The pointer used to scan the pattern */ 931*7c478bd9Sstevel@tonic-gate int invert = 0; /* True to invert the sense of the match */ 932*7c478bd9Sstevel@tonic-gate int matched = 0; /* True if the character matched the pattern */ 933*7c478bd9Sstevel@tonic-gate /* 934*7c478bd9Sstevel@tonic-gate * If the first character is a caret, the sense of the match is 935*7c478bd9Sstevel@tonic-gate * inverted and only if the character isn't one of those in the 936*7c478bd9Sstevel@tonic-gate * range, do we say that it matches. 937*7c478bd9Sstevel@tonic-gate */ 938*7c478bd9Sstevel@tonic-gate if(*pptr == '^') { 939*7c478bd9Sstevel@tonic-gate pptr++; 940*7c478bd9Sstevel@tonic-gate invert = 1; 941*7c478bd9Sstevel@tonic-gate }; 942*7c478bd9Sstevel@tonic-gate /* 943*7c478bd9Sstevel@tonic-gate * The hyphen is only a special character when it follows the first 944*7c478bd9Sstevel@tonic-gate * character of the range (not including the caret). 945*7c478bd9Sstevel@tonic-gate */ 946*7c478bd9Sstevel@tonic-gate if(*pptr == '-') { 947*7c478bd9Sstevel@tonic-gate pptr++; 948*7c478bd9Sstevel@tonic-gate if(c == '-') { 949*7c478bd9Sstevel@tonic-gate *endp = pptr; 950*7c478bd9Sstevel@tonic-gate matched = 1; 951*7c478bd9Sstevel@tonic-gate }; 952*7c478bd9Sstevel@tonic-gate /* 953*7c478bd9Sstevel@tonic-gate * Skip other leading '-' characters since they make no sense. 954*7c478bd9Sstevel@tonic-gate */ 955*7c478bd9Sstevel@tonic-gate while(*pptr == '-') 956*7c478bd9Sstevel@tonic-gate pptr++; 957*7c478bd9Sstevel@tonic-gate }; 958*7c478bd9Sstevel@tonic-gate /* 959*7c478bd9Sstevel@tonic-gate * The hyphen is only a special character when it follows the first 960*7c478bd9Sstevel@tonic-gate * character of the range (not including the caret or a hyphen). 961*7c478bd9Sstevel@tonic-gate */ 962*7c478bd9Sstevel@tonic-gate if(*pptr == ']') { 963*7c478bd9Sstevel@tonic-gate pptr++; 964*7c478bd9Sstevel@tonic-gate if(c == ']') { 965*7c478bd9Sstevel@tonic-gate *endp = pptr; 966*7c478bd9Sstevel@tonic-gate matched = 1; 967*7c478bd9Sstevel@tonic-gate }; 968*7c478bd9Sstevel@tonic-gate }; 969*7c478bd9Sstevel@tonic-gate /* 970*7c478bd9Sstevel@tonic-gate * Having dealt with the characters that have special meanings at 971*7c478bd9Sstevel@tonic-gate * the beginning of a character range expression, see if the 972*7c478bd9Sstevel@tonic-gate * character matches any of the remaining characters of the range, 973*7c478bd9Sstevel@tonic-gate * up until a terminating ']' character is seen. 974*7c478bd9Sstevel@tonic-gate */ 975*7c478bd9Sstevel@tonic-gate while(!matched && *pptr && *pptr != ']') { 976*7c478bd9Sstevel@tonic-gate /* 977*7c478bd9Sstevel@tonic-gate * Is this a range of characters signaled by the two end characters 978*7c478bd9Sstevel@tonic-gate * separated by a hyphen? 979*7c478bd9Sstevel@tonic-gate */ 980*7c478bd9Sstevel@tonic-gate if(*pptr == '-') { 981*7c478bd9Sstevel@tonic-gate if(pptr[1] != ']') { 982*7c478bd9Sstevel@tonic-gate if(c >= pptr[-1] && c <= pptr[1]) 983*7c478bd9Sstevel@tonic-gate matched = 1; 984*7c478bd9Sstevel@tonic-gate pptr += 2; 985*7c478bd9Sstevel@tonic-gate }; 986*7c478bd9Sstevel@tonic-gate /* 987*7c478bd9Sstevel@tonic-gate * A normal character to be compared directly. 988*7c478bd9Sstevel@tonic-gate */ 989*7c478bd9Sstevel@tonic-gate } else if(*pptr++ == c) { 990*7c478bd9Sstevel@tonic-gate matched = 1; 991*7c478bd9Sstevel@tonic-gate }; 992*7c478bd9Sstevel@tonic-gate }; 993*7c478bd9Sstevel@tonic-gate /* 994*7c478bd9Sstevel@tonic-gate * Find the terminating ']'. 995*7c478bd9Sstevel@tonic-gate */ 996*7c478bd9Sstevel@tonic-gate while(*pptr && *pptr != ']') 997*7c478bd9Sstevel@tonic-gate pptr++; 998*7c478bd9Sstevel@tonic-gate /* 999*7c478bd9Sstevel@tonic-gate * Did we find a terminating ']'? 1000*7c478bd9Sstevel@tonic-gate */ 1001*7c478bd9Sstevel@tonic-gate if(*pptr == ']') { 1002*7c478bd9Sstevel@tonic-gate *endp = pptr + 1; 1003*7c478bd9Sstevel@tonic-gate return matched ? !invert : invert; 1004*7c478bd9Sstevel@tonic-gate }; 1005*7c478bd9Sstevel@tonic-gate /* 1006*7c478bd9Sstevel@tonic-gate * If the pattern didn't end with a ']' then it doesn't match, regardless 1007*7c478bd9Sstevel@tonic-gate * of the value of the required sense of the match. 1008*7c478bd9Sstevel@tonic-gate */ 1009*7c478bd9Sstevel@tonic-gate *endp = pptr; 1010*7c478bd9Sstevel@tonic-gate return 0; 1011*7c478bd9Sstevel@tonic-gate } 1012*7c478bd9Sstevel@tonic-gate 1013*7c478bd9Sstevel@tonic-gate /*....................................................................... 1014*7c478bd9Sstevel@tonic-gate * This is a qsort() comparison function used to sort strings. 1015*7c478bd9Sstevel@tonic-gate * 1016*7c478bd9Sstevel@tonic-gate * Input: 1017*7c478bd9Sstevel@tonic-gate * v1, v2 void * Pointers to the two strings to be compared. 1018*7c478bd9Sstevel@tonic-gate * Output: 1019*7c478bd9Sstevel@tonic-gate * return int -1 -> v1 < v2. 1020*7c478bd9Sstevel@tonic-gate * 0 -> v1 == v2 1021*7c478bd9Sstevel@tonic-gate * 1 -> v1 > v2 1022*7c478bd9Sstevel@tonic-gate */ 1023*7c478bd9Sstevel@tonic-gate static int ef_cmp_strings(const void *v1, const void *v2) 1024*7c478bd9Sstevel@tonic-gate { 1025*7c478bd9Sstevel@tonic-gate char * const *s1 = (char * const *) v1; 1026*7c478bd9Sstevel@tonic-gate char * const *s2 = (char * const *) v2; 1027*7c478bd9Sstevel@tonic-gate return strcmp(*s1, *s2); 1028*7c478bd9Sstevel@tonic-gate } 1029*7c478bd9Sstevel@tonic-gate 1030*7c478bd9Sstevel@tonic-gate /*....................................................................... 1031*7c478bd9Sstevel@tonic-gate * Preprocess a path, expanding ~/, ~user/ and $envvar references, using 1032*7c478bd9Sstevel@tonic-gate * ef->path as a work buffer, then copy the result into a cache entry, 1033*7c478bd9Sstevel@tonic-gate * and return a pointer to this copy. 1034*7c478bd9Sstevel@tonic-gate * 1035*7c478bd9Sstevel@tonic-gate * Input: 1036*7c478bd9Sstevel@tonic-gate * ef ExpandFile * The resource object of the file matcher. 1037*7c478bd9Sstevel@tonic-gate * pathlen int The length of the prefix of path[] to be expanded. 1038*7c478bd9Sstevel@tonic-gate * Output: 1039*7c478bd9Sstevel@tonic-gate * return char * A pointer to a copy of the output path in the 1040*7c478bd9Sstevel@tonic-gate * cache. On error NULL is returned, and a description 1041*7c478bd9Sstevel@tonic-gate * of the error is left in ef->err. 1042*7c478bd9Sstevel@tonic-gate */ 1043*7c478bd9Sstevel@tonic-gate static char *ef_expand_special(ExpandFile *ef, const char *path, int pathlen) 1044*7c478bd9Sstevel@tonic-gate { 1045*7c478bd9Sstevel@tonic-gate int spos; /* The index of the start of the path segment that needs */ 1046*7c478bd9Sstevel@tonic-gate /* to be copied from path[] to the output pathname. */ 1047*7c478bd9Sstevel@tonic-gate int ppos; /* The index of a character in path[] */ 1048*7c478bd9Sstevel@tonic-gate char *pptr; /* A pointer into the output path */ 1049*7c478bd9Sstevel@tonic-gate int escaped; /* True if the previous character was a '\' */ 1050*7c478bd9Sstevel@tonic-gate int i; 1051*7c478bd9Sstevel@tonic-gate /* 1052*7c478bd9Sstevel@tonic-gate * Clear the pathname buffer. 1053*7c478bd9Sstevel@tonic-gate */ 1054*7c478bd9Sstevel@tonic-gate _pn_clear_path(ef->path); 1055*7c478bd9Sstevel@tonic-gate /* 1056*7c478bd9Sstevel@tonic-gate * We need to perform two passes, one to expand environment variables 1057*7c478bd9Sstevel@tonic-gate * and a second to do tilde expansion. This caters for the case 1058*7c478bd9Sstevel@tonic-gate * where an initial dollar expansion yields a tilde expression. 1059*7c478bd9Sstevel@tonic-gate */ 1060*7c478bd9Sstevel@tonic-gate escaped = 0; 1061*7c478bd9Sstevel@tonic-gate for(spos=ppos=0; ppos < pathlen; ppos++) { 1062*7c478bd9Sstevel@tonic-gate int c = path[ppos]; 1063*7c478bd9Sstevel@tonic-gate if(escaped) { 1064*7c478bd9Sstevel@tonic-gate escaped = 0; 1065*7c478bd9Sstevel@tonic-gate } else if(c == '\\') { 1066*7c478bd9Sstevel@tonic-gate escaped = 1; 1067*7c478bd9Sstevel@tonic-gate } else if(c == '$') { 1068*7c478bd9Sstevel@tonic-gate int envlen; /* The length of the environment variable */ 1069*7c478bd9Sstevel@tonic-gate char *value; /* The value of the environment variable */ 1070*7c478bd9Sstevel@tonic-gate /* 1071*7c478bd9Sstevel@tonic-gate * Record the preceding unrecorded part of the pathname. 1072*7c478bd9Sstevel@tonic-gate */ 1073*7c478bd9Sstevel@tonic-gate if(spos < ppos && _pn_append_to_path(ef->path, path + spos, ppos-spos, 0) 1074*7c478bd9Sstevel@tonic-gate == NULL) { 1075*7c478bd9Sstevel@tonic-gate _err_record_msg(ef->err, "Insufficient memory to expand path", 1076*7c478bd9Sstevel@tonic-gate END_ERR_MSG); 1077*7c478bd9Sstevel@tonic-gate return NULL; 1078*7c478bd9Sstevel@tonic-gate }; 1079*7c478bd9Sstevel@tonic-gate /* 1080*7c478bd9Sstevel@tonic-gate * Skip the dollar. 1081*7c478bd9Sstevel@tonic-gate */ 1082*7c478bd9Sstevel@tonic-gate ppos++; 1083*7c478bd9Sstevel@tonic-gate /* 1084*7c478bd9Sstevel@tonic-gate * Copy the environment variable name that follows the dollar into 1085*7c478bd9Sstevel@tonic-gate * ef->envnam[], stopping if a directory separator or end of string 1086*7c478bd9Sstevel@tonic-gate * is seen. 1087*7c478bd9Sstevel@tonic-gate */ 1088*7c478bd9Sstevel@tonic-gate for(envlen=0; envlen<ENV_LEN && ppos < pathlen && 1089*7c478bd9Sstevel@tonic-gate strncmp(path + ppos, FS_DIR_SEP, FS_DIR_SEP_LEN); envlen++) 1090*7c478bd9Sstevel@tonic-gate ef->envnam[envlen] = path[ppos++]; 1091*7c478bd9Sstevel@tonic-gate /* 1092*7c478bd9Sstevel@tonic-gate * If the username overflowed the buffer, treat it as invalid (note that 1093*7c478bd9Sstevel@tonic-gate * on most unix systems only 8 characters are allowed in a username, 1094*7c478bd9Sstevel@tonic-gate * whereas our ENV_LEN is much bigger than that. 1095*7c478bd9Sstevel@tonic-gate */ 1096*7c478bd9Sstevel@tonic-gate if(envlen >= ENV_LEN) { 1097*7c478bd9Sstevel@tonic-gate _err_record_msg(ef->err, "Environment variable name too long", 1098*7c478bd9Sstevel@tonic-gate END_ERR_MSG); 1099*7c478bd9Sstevel@tonic-gate return NULL; 1100*7c478bd9Sstevel@tonic-gate }; 1101*7c478bd9Sstevel@tonic-gate /* 1102*7c478bd9Sstevel@tonic-gate * Terminate the environment variable name. 1103*7c478bd9Sstevel@tonic-gate */ 1104*7c478bd9Sstevel@tonic-gate ef->envnam[envlen] = '\0'; 1105*7c478bd9Sstevel@tonic-gate /* 1106*7c478bd9Sstevel@tonic-gate * Lookup the value of the environment variable. 1107*7c478bd9Sstevel@tonic-gate */ 1108*7c478bd9Sstevel@tonic-gate value = getenv(ef->envnam); 1109*7c478bd9Sstevel@tonic-gate if(!value) { 1110*7c478bd9Sstevel@tonic-gate _err_record_msg(ef->err, "No expansion found for: $", ef->envnam, 1111*7c478bd9Sstevel@tonic-gate END_ERR_MSG); 1112*7c478bd9Sstevel@tonic-gate return NULL; 1113*7c478bd9Sstevel@tonic-gate }; 1114*7c478bd9Sstevel@tonic-gate /* 1115*7c478bd9Sstevel@tonic-gate * Copy the value of the environment variable into the output pathname. 1116*7c478bd9Sstevel@tonic-gate */ 1117*7c478bd9Sstevel@tonic-gate if(_pn_append_to_path(ef->path, value, -1, 0) == NULL) { 1118*7c478bd9Sstevel@tonic-gate _err_record_msg(ef->err, "Insufficient memory to expand path", 1119*7c478bd9Sstevel@tonic-gate END_ERR_MSG); 1120*7c478bd9Sstevel@tonic-gate return NULL; 1121*7c478bd9Sstevel@tonic-gate }; 1122*7c478bd9Sstevel@tonic-gate /* 1123*7c478bd9Sstevel@tonic-gate * Record the start of the uncopied tail of the input pathname. 1124*7c478bd9Sstevel@tonic-gate */ 1125*7c478bd9Sstevel@tonic-gate spos = ppos; 1126*7c478bd9Sstevel@tonic-gate }; 1127*7c478bd9Sstevel@tonic-gate }; 1128*7c478bd9Sstevel@tonic-gate /* 1129*7c478bd9Sstevel@tonic-gate * Record the uncopied tail of the pathname. 1130*7c478bd9Sstevel@tonic-gate */ 1131*7c478bd9Sstevel@tonic-gate if(spos < ppos && _pn_append_to_path(ef->path, path + spos, ppos-spos, 0) 1132*7c478bd9Sstevel@tonic-gate == NULL) { 1133*7c478bd9Sstevel@tonic-gate _err_record_msg(ef->err, "Insufficient memory to expand path", END_ERR_MSG); 1134*7c478bd9Sstevel@tonic-gate return NULL; 1135*7c478bd9Sstevel@tonic-gate }; 1136*7c478bd9Sstevel@tonic-gate /* 1137*7c478bd9Sstevel@tonic-gate * If the first character of the resulting pathname is a tilde, 1138*7c478bd9Sstevel@tonic-gate * then attempt to substitute the home directory of the specified user. 1139*7c478bd9Sstevel@tonic-gate */ 1140*7c478bd9Sstevel@tonic-gate pptr = ef->path->name; 1141*7c478bd9Sstevel@tonic-gate if(*pptr == '~' && path[0] != '\\') { 1142*7c478bd9Sstevel@tonic-gate int usrlen; /* The length of the username following the tilde */ 1143*7c478bd9Sstevel@tonic-gate const char *homedir; /* The home directory of the user */ 1144*7c478bd9Sstevel@tonic-gate int homelen; /* The length of the home directory string */ 1145*7c478bd9Sstevel@tonic-gate int plen; /* The current length of the path */ 1146*7c478bd9Sstevel@tonic-gate int skip=0; /* The number of characters to skip after the ~user */ 1147*7c478bd9Sstevel@tonic-gate /* 1148*7c478bd9Sstevel@tonic-gate * Get the current length of the output path. 1149*7c478bd9Sstevel@tonic-gate */ 1150*7c478bd9Sstevel@tonic-gate plen = strlen(ef->path->name); 1151*7c478bd9Sstevel@tonic-gate /* 1152*7c478bd9Sstevel@tonic-gate * Skip the tilde. 1153*7c478bd9Sstevel@tonic-gate */ 1154*7c478bd9Sstevel@tonic-gate pptr++; 1155*7c478bd9Sstevel@tonic-gate /* 1156*7c478bd9Sstevel@tonic-gate * Copy the optional username that follows the tilde into ef->usrnam[]. 1157*7c478bd9Sstevel@tonic-gate */ 1158*7c478bd9Sstevel@tonic-gate for(usrlen=0; usrlen<USR_LEN && *pptr && 1159*7c478bd9Sstevel@tonic-gate strncmp(pptr, FS_DIR_SEP, FS_DIR_SEP_LEN); usrlen++) 1160*7c478bd9Sstevel@tonic-gate ef->usrnam[usrlen] = *pptr++; 1161*7c478bd9Sstevel@tonic-gate /* 1162*7c478bd9Sstevel@tonic-gate * If the username overflowed the buffer, treat it as invalid (note that 1163*7c478bd9Sstevel@tonic-gate * on most unix systems only 8 characters are allowed in a username, 1164*7c478bd9Sstevel@tonic-gate * whereas our USR_LEN is much bigger than that. 1165*7c478bd9Sstevel@tonic-gate */ 1166*7c478bd9Sstevel@tonic-gate if(usrlen >= USR_LEN) { 1167*7c478bd9Sstevel@tonic-gate _err_record_msg(ef->err, "Username too long", END_ERR_MSG); 1168*7c478bd9Sstevel@tonic-gate return NULL; 1169*7c478bd9Sstevel@tonic-gate }; 1170*7c478bd9Sstevel@tonic-gate /* 1171*7c478bd9Sstevel@tonic-gate * Terminate the username string. 1172*7c478bd9Sstevel@tonic-gate */ 1173*7c478bd9Sstevel@tonic-gate ef->usrnam[usrlen] = '\0'; 1174*7c478bd9Sstevel@tonic-gate /* 1175*7c478bd9Sstevel@tonic-gate * Lookup the home directory of the user. 1176*7c478bd9Sstevel@tonic-gate */ 1177*7c478bd9Sstevel@tonic-gate homedir = _hd_lookup_home_dir(ef->home, ef->usrnam); 1178*7c478bd9Sstevel@tonic-gate if(!homedir) { 1179*7c478bd9Sstevel@tonic-gate _err_record_msg(ef->err, _hd_last_home_dir_error(ef->home), END_ERR_MSG); 1180*7c478bd9Sstevel@tonic-gate return NULL; 1181*7c478bd9Sstevel@tonic-gate }; 1182*7c478bd9Sstevel@tonic-gate homelen = strlen(homedir); 1183*7c478bd9Sstevel@tonic-gate /* 1184*7c478bd9Sstevel@tonic-gate * ~user and ~ are usually followed by a directory separator to 1185*7c478bd9Sstevel@tonic-gate * separate them from the file contained in the home directory. 1186*7c478bd9Sstevel@tonic-gate * If the home directory is the root directory, then we don't want 1187*7c478bd9Sstevel@tonic-gate * to follow the home directory by a directory separator, so we must 1188*7c478bd9Sstevel@tonic-gate * erase it. 1189*7c478bd9Sstevel@tonic-gate */ 1190*7c478bd9Sstevel@tonic-gate if(strcmp(homedir, FS_ROOT_DIR) == 0 && 1191*7c478bd9Sstevel@tonic-gate strncmp(pptr, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0) { 1192*7c478bd9Sstevel@tonic-gate skip = FS_DIR_SEP_LEN; 1193*7c478bd9Sstevel@tonic-gate }; 1194*7c478bd9Sstevel@tonic-gate /* 1195*7c478bd9Sstevel@tonic-gate * If needed, increase the size of the pathname buffer to allow it 1196*7c478bd9Sstevel@tonic-gate * to accomodate the home directory instead of the tilde expression. 1197*7c478bd9Sstevel@tonic-gate * Note that pptr may not be valid after this call. 1198*7c478bd9Sstevel@tonic-gate */ 1199*7c478bd9Sstevel@tonic-gate if(_pn_resize_path(ef->path, plen - usrlen - 1 - skip + homelen)==NULL) { 1200*7c478bd9Sstevel@tonic-gate _err_record_msg(ef->err, "Insufficient memory to expand filename", 1201*7c478bd9Sstevel@tonic-gate END_ERR_MSG); 1202*7c478bd9Sstevel@tonic-gate return NULL; 1203*7c478bd9Sstevel@tonic-gate }; 1204*7c478bd9Sstevel@tonic-gate /* 1205*7c478bd9Sstevel@tonic-gate * Move the part of the pathname that follows the tilde expression to 1206*7c478bd9Sstevel@tonic-gate * the end of where the home directory will need to be inserted. 1207*7c478bd9Sstevel@tonic-gate */ 1208*7c478bd9Sstevel@tonic-gate memmove(ef->path->name + homelen, 1209*7c478bd9Sstevel@tonic-gate ef->path->name + 1 + usrlen + skip, plen - usrlen - 1 - skip+1); 1210*7c478bd9Sstevel@tonic-gate /* 1211*7c478bd9Sstevel@tonic-gate * Write the home directory at the beginning of the string. 1212*7c478bd9Sstevel@tonic-gate */ 1213*7c478bd9Sstevel@tonic-gate for(i=0; i<homelen; i++) 1214*7c478bd9Sstevel@tonic-gate ef->path->name[i] = homedir[i]; 1215*7c478bd9Sstevel@tonic-gate }; 1216*7c478bd9Sstevel@tonic-gate /* 1217*7c478bd9Sstevel@tonic-gate * Copy the result into the cache, and return a pointer to the copy. 1218*7c478bd9Sstevel@tonic-gate */ 1219*7c478bd9Sstevel@tonic-gate return ef_cache_pathname(ef, ef->path->name, 0); 1220*7c478bd9Sstevel@tonic-gate } 1221*7c478bd9Sstevel@tonic-gate 1222*7c478bd9Sstevel@tonic-gate /*....................................................................... 1223*7c478bd9Sstevel@tonic-gate * Return a description of the last path-expansion error that occurred. 1224*7c478bd9Sstevel@tonic-gate * 1225*7c478bd9Sstevel@tonic-gate * Input: 1226*7c478bd9Sstevel@tonic-gate * ef ExpandFile * The path-expansion resource object. 1227*7c478bd9Sstevel@tonic-gate * Output: 1228*7c478bd9Sstevel@tonic-gate * return char * The description of the last error. 1229*7c478bd9Sstevel@tonic-gate */ 1230*7c478bd9Sstevel@tonic-gate const char *ef_last_error(ExpandFile *ef) 1231*7c478bd9Sstevel@tonic-gate { 1232*7c478bd9Sstevel@tonic-gate return ef ? _err_get_msg(ef->err) : "NULL ExpandFile argument"; 1233*7c478bd9Sstevel@tonic-gate } 1234*7c478bd9Sstevel@tonic-gate 1235*7c478bd9Sstevel@tonic-gate /*....................................................................... 1236*7c478bd9Sstevel@tonic-gate * Print out an array of matching files. 1237*7c478bd9Sstevel@tonic-gate * 1238*7c478bd9Sstevel@tonic-gate * Input: 1239*7c478bd9Sstevel@tonic-gate * result FileExpansion * The container of the sorted array of 1240*7c478bd9Sstevel@tonic-gate * expansions. 1241*7c478bd9Sstevel@tonic-gate * fp FILE * The output stream to write to. 1242*7c478bd9Sstevel@tonic-gate * term_width int The width of the terminal. 1243*7c478bd9Sstevel@tonic-gate * Output: 1244*7c478bd9Sstevel@tonic-gate * return int 0 - OK. 1245*7c478bd9Sstevel@tonic-gate * 1 - Error. 1246*7c478bd9Sstevel@tonic-gate */ 1247*7c478bd9Sstevel@tonic-gate int ef_list_expansions(FileExpansion *result, FILE *fp, int term_width) 1248*7c478bd9Sstevel@tonic-gate { 1249*7c478bd9Sstevel@tonic-gate return _ef_output_expansions(result, _io_write_stdio, fp, term_width); 1250*7c478bd9Sstevel@tonic-gate } 1251*7c478bd9Sstevel@tonic-gate 1252*7c478bd9Sstevel@tonic-gate /*....................................................................... 1253*7c478bd9Sstevel@tonic-gate * Print out an array of matching files via a callback. 1254*7c478bd9Sstevel@tonic-gate * 1255*7c478bd9Sstevel@tonic-gate * Input: 1256*7c478bd9Sstevel@tonic-gate * result FileExpansion * The container of the sorted array of 1257*7c478bd9Sstevel@tonic-gate * expansions. 1258*7c478bd9Sstevel@tonic-gate * write_fn GlWriteFn * The function to call to write the 1259*7c478bd9Sstevel@tonic-gate * expansions or 0 to discard the output. 1260*7c478bd9Sstevel@tonic-gate * data void * Anonymous data to pass to write_fn(). 1261*7c478bd9Sstevel@tonic-gate * term_width int The width of the terminal. 1262*7c478bd9Sstevel@tonic-gate * Output: 1263*7c478bd9Sstevel@tonic-gate * return int 0 - OK. 1264*7c478bd9Sstevel@tonic-gate * 1 - Error. 1265*7c478bd9Sstevel@tonic-gate */ 1266*7c478bd9Sstevel@tonic-gate int _ef_output_expansions(FileExpansion *result, GlWriteFn *write_fn, 1267*7c478bd9Sstevel@tonic-gate void *data, int term_width) 1268*7c478bd9Sstevel@tonic-gate { 1269*7c478bd9Sstevel@tonic-gate EfListFormat fmt; /* List formatting information */ 1270*7c478bd9Sstevel@tonic-gate int lnum; /* The sequential number of the line to print next */ 1271*7c478bd9Sstevel@tonic-gate /* 1272*7c478bd9Sstevel@tonic-gate * Not enough space to list anything? 1273*7c478bd9Sstevel@tonic-gate */ 1274*7c478bd9Sstevel@tonic-gate if(term_width < 1) 1275*7c478bd9Sstevel@tonic-gate return 0; 1276*7c478bd9Sstevel@tonic-gate /* 1277*7c478bd9Sstevel@tonic-gate * Do we have a callback to write via, and any expansions to be listed? 1278*7c478bd9Sstevel@tonic-gate */ 1279*7c478bd9Sstevel@tonic-gate if(write_fn && result && result->nfile>0) { 1280*7c478bd9Sstevel@tonic-gate /* 1281*7c478bd9Sstevel@tonic-gate * Work out how to arrange the listing into fixed sized columns. 1282*7c478bd9Sstevel@tonic-gate */ 1283*7c478bd9Sstevel@tonic-gate ef_plan_listing(result, term_width, &fmt); 1284*7c478bd9Sstevel@tonic-gate /* 1285*7c478bd9Sstevel@tonic-gate * Print the listing to the specified stream. 1286*7c478bd9Sstevel@tonic-gate */ 1287*7c478bd9Sstevel@tonic-gate for(lnum=0; lnum < fmt.nline; lnum++) { 1288*7c478bd9Sstevel@tonic-gate if(ef_format_line(result, &fmt, lnum, write_fn, data)) 1289*7c478bd9Sstevel@tonic-gate return 1; 1290*7c478bd9Sstevel@tonic-gate }; 1291*7c478bd9Sstevel@tonic-gate }; 1292*7c478bd9Sstevel@tonic-gate return 0; 1293*7c478bd9Sstevel@tonic-gate } 1294*7c478bd9Sstevel@tonic-gate 1295*7c478bd9Sstevel@tonic-gate /*....................................................................... 1296*7c478bd9Sstevel@tonic-gate * Work out how to arrange a given array of completions into a listing 1297*7c478bd9Sstevel@tonic-gate * of one or more fixed size columns. 1298*7c478bd9Sstevel@tonic-gate * 1299*7c478bd9Sstevel@tonic-gate * Input: 1300*7c478bd9Sstevel@tonic-gate * result FileExpansion * The set of completions to be listed. 1301*7c478bd9Sstevel@tonic-gate * term_width int The width of the terminal. A lower limit of 1302*7c478bd9Sstevel@tonic-gate * zero is quietly enforced. 1303*7c478bd9Sstevel@tonic-gate * Input/Output: 1304*7c478bd9Sstevel@tonic-gate * fmt EfListFormat * The formatting information will be assigned 1305*7c478bd9Sstevel@tonic-gate * to the members of *fmt. 1306*7c478bd9Sstevel@tonic-gate */ 1307*7c478bd9Sstevel@tonic-gate static void ef_plan_listing(FileExpansion *result, int term_width, 1308*7c478bd9Sstevel@tonic-gate EfListFormat *fmt) 1309*7c478bd9Sstevel@tonic-gate { 1310*7c478bd9Sstevel@tonic-gate int maxlen; /* The length of the longest matching string */ 1311*7c478bd9Sstevel@tonic-gate int i; 1312*7c478bd9Sstevel@tonic-gate /* 1313*7c478bd9Sstevel@tonic-gate * Ensure that term_width >= 0. 1314*7c478bd9Sstevel@tonic-gate */ 1315*7c478bd9Sstevel@tonic-gate if(term_width < 0) 1316*7c478bd9Sstevel@tonic-gate term_width = 0; 1317*7c478bd9Sstevel@tonic-gate /* 1318*7c478bd9Sstevel@tonic-gate * Start by assuming the worst case, that either nothing will fit 1319*7c478bd9Sstevel@tonic-gate * on the screen, or that there are no matches to be listed. 1320*7c478bd9Sstevel@tonic-gate */ 1321*7c478bd9Sstevel@tonic-gate fmt->term_width = term_width; 1322*7c478bd9Sstevel@tonic-gate fmt->column_width = 0; 1323*7c478bd9Sstevel@tonic-gate fmt->nline = fmt->ncol = 0; 1324*7c478bd9Sstevel@tonic-gate /* 1325*7c478bd9Sstevel@tonic-gate * Work out the maximum length of the matching strings. 1326*7c478bd9Sstevel@tonic-gate */ 1327*7c478bd9Sstevel@tonic-gate maxlen = 0; 1328*7c478bd9Sstevel@tonic-gate for(i=0; i<result->nfile; i++) { 1329*7c478bd9Sstevel@tonic-gate int len = strlen(result->files[i]); 1330*7c478bd9Sstevel@tonic-gate if(len > maxlen) 1331*7c478bd9Sstevel@tonic-gate maxlen = len; 1332*7c478bd9Sstevel@tonic-gate }; 1333*7c478bd9Sstevel@tonic-gate /* 1334*7c478bd9Sstevel@tonic-gate * Nothing to list? 1335*7c478bd9Sstevel@tonic-gate */ 1336*7c478bd9Sstevel@tonic-gate if(maxlen == 0) 1337*7c478bd9Sstevel@tonic-gate return; 1338*7c478bd9Sstevel@tonic-gate /* 1339*7c478bd9Sstevel@tonic-gate * Split the available terminal width into columns of 1340*7c478bd9Sstevel@tonic-gate * maxlen + EF_COL_SEP characters. 1341*7c478bd9Sstevel@tonic-gate */ 1342*7c478bd9Sstevel@tonic-gate fmt->column_width = maxlen; 1343*7c478bd9Sstevel@tonic-gate fmt->ncol = fmt->term_width / (fmt->column_width + EF_COL_SEP); 1344*7c478bd9Sstevel@tonic-gate /* 1345*7c478bd9Sstevel@tonic-gate * If the column width is greater than the terminal width, zero columns 1346*7c478bd9Sstevel@tonic-gate * will have been selected. Set a lower limit of one column. Leave it 1347*7c478bd9Sstevel@tonic-gate * up to the caller how to deal with completions who's widths exceed 1348*7c478bd9Sstevel@tonic-gate * the available terminal width. 1349*7c478bd9Sstevel@tonic-gate */ 1350*7c478bd9Sstevel@tonic-gate if(fmt->ncol < 1) 1351*7c478bd9Sstevel@tonic-gate fmt->ncol = 1; 1352*7c478bd9Sstevel@tonic-gate /* 1353*7c478bd9Sstevel@tonic-gate * How many lines of output will be needed? 1354*7c478bd9Sstevel@tonic-gate */ 1355*7c478bd9Sstevel@tonic-gate fmt->nline = (result->nfile + fmt->ncol - 1) / fmt->ncol; 1356*7c478bd9Sstevel@tonic-gate return; 1357*7c478bd9Sstevel@tonic-gate } 1358*7c478bd9Sstevel@tonic-gate 1359*7c478bd9Sstevel@tonic-gate /*....................................................................... 1360*7c478bd9Sstevel@tonic-gate * Render one line of a multi-column listing of completions, using a 1361*7c478bd9Sstevel@tonic-gate * callback function to pass the output to an arbitrary destination. 1362*7c478bd9Sstevel@tonic-gate * 1363*7c478bd9Sstevel@tonic-gate * Input: 1364*7c478bd9Sstevel@tonic-gate * result FileExpansion * The container of the sorted array of 1365*7c478bd9Sstevel@tonic-gate * completions. 1366*7c478bd9Sstevel@tonic-gate * fmt EfListFormat * Formatting information. 1367*7c478bd9Sstevel@tonic-gate * lnum int The index of the line to print, starting 1368*7c478bd9Sstevel@tonic-gate * from 0, and incrementing until the return 1369*7c478bd9Sstevel@tonic-gate * value indicates that there is nothing more 1370*7c478bd9Sstevel@tonic-gate * to be printed. 1371*7c478bd9Sstevel@tonic-gate * write_fn GlWriteFn * The function to call to write the line, or 1372*7c478bd9Sstevel@tonic-gate * 0 to discard the output. 1373*7c478bd9Sstevel@tonic-gate * data void * Anonymous data to pass to write_fn(). 1374*7c478bd9Sstevel@tonic-gate * Output: 1375*7c478bd9Sstevel@tonic-gate * return int 0 - Line printed ok. 1376*7c478bd9Sstevel@tonic-gate * 1 - Nothing to print. 1377*7c478bd9Sstevel@tonic-gate */ 1378*7c478bd9Sstevel@tonic-gate static int ef_format_line(FileExpansion *result, EfListFormat *fmt, int lnum, 1379*7c478bd9Sstevel@tonic-gate GlWriteFn *write_fn, void *data) 1380*7c478bd9Sstevel@tonic-gate { 1381*7c478bd9Sstevel@tonic-gate int col; /* The index of the list column being output */ 1382*7c478bd9Sstevel@tonic-gate /* 1383*7c478bd9Sstevel@tonic-gate * If the line index is out of bounds, there is nothing to be written. 1384*7c478bd9Sstevel@tonic-gate */ 1385*7c478bd9Sstevel@tonic-gate if(lnum < 0 || lnum >= fmt->nline) 1386*7c478bd9Sstevel@tonic-gate return 1; 1387*7c478bd9Sstevel@tonic-gate /* 1388*7c478bd9Sstevel@tonic-gate * If no output function has been provided, return as though the line 1389*7c478bd9Sstevel@tonic-gate * had been printed. 1390*7c478bd9Sstevel@tonic-gate */ 1391*7c478bd9Sstevel@tonic-gate if(!write_fn) 1392*7c478bd9Sstevel@tonic-gate return 0; 1393*7c478bd9Sstevel@tonic-gate /* 1394*7c478bd9Sstevel@tonic-gate * Print the matches in 'ncol' columns, sorted in line order within each 1395*7c478bd9Sstevel@tonic-gate * column. 1396*7c478bd9Sstevel@tonic-gate */ 1397*7c478bd9Sstevel@tonic-gate for(col=0; col < fmt->ncol; col++) { 1398*7c478bd9Sstevel@tonic-gate int m = col*fmt->nline + lnum; 1399*7c478bd9Sstevel@tonic-gate /* 1400*7c478bd9Sstevel@tonic-gate * Is there another match to be written? Note that in general 1401*7c478bd9Sstevel@tonic-gate * the last line of a listing will have fewer filled columns 1402*7c478bd9Sstevel@tonic-gate * than the initial lines. 1403*7c478bd9Sstevel@tonic-gate */ 1404*7c478bd9Sstevel@tonic-gate if(m < result->nfile) { 1405*7c478bd9Sstevel@tonic-gate char *file = result->files[m]; 1406*7c478bd9Sstevel@tonic-gate /* 1407*7c478bd9Sstevel@tonic-gate * How long are the completion and type-suffix strings? 1408*7c478bd9Sstevel@tonic-gate */ 1409*7c478bd9Sstevel@tonic-gate int flen = strlen(file); 1410*7c478bd9Sstevel@tonic-gate /* 1411*7c478bd9Sstevel@tonic-gate * Write the completion string. 1412*7c478bd9Sstevel@tonic-gate */ 1413*7c478bd9Sstevel@tonic-gate if(write_fn(data, file, flen) != flen) 1414*7c478bd9Sstevel@tonic-gate return 1; 1415*7c478bd9Sstevel@tonic-gate /* 1416*7c478bd9Sstevel@tonic-gate * If another column follows the current one, pad to its start with spaces. 1417*7c478bd9Sstevel@tonic-gate */ 1418*7c478bd9Sstevel@tonic-gate if(col+1 < fmt->ncol) { 1419*7c478bd9Sstevel@tonic-gate /* 1420*7c478bd9Sstevel@tonic-gate * The following constant string of spaces is used to pad the output. 1421*7c478bd9Sstevel@tonic-gate */ 1422*7c478bd9Sstevel@tonic-gate static const char spaces[] = " "; 1423*7c478bd9Sstevel@tonic-gate static const int nspace = sizeof(spaces) - 1; 1424*7c478bd9Sstevel@tonic-gate /* 1425*7c478bd9Sstevel@tonic-gate * Pad to the next column, using as few sub-strings of the spaces[] 1426*7c478bd9Sstevel@tonic-gate * array as possible. 1427*7c478bd9Sstevel@tonic-gate */ 1428*7c478bd9Sstevel@tonic-gate int npad = fmt->column_width + EF_COL_SEP - flen; 1429*7c478bd9Sstevel@tonic-gate while(npad>0) { 1430*7c478bd9Sstevel@tonic-gate int n = npad > nspace ? nspace : npad; 1431*7c478bd9Sstevel@tonic-gate if(write_fn(data, spaces + nspace - n, n) != n) 1432*7c478bd9Sstevel@tonic-gate return 1; 1433*7c478bd9Sstevel@tonic-gate npad -= n; 1434*7c478bd9Sstevel@tonic-gate }; 1435*7c478bd9Sstevel@tonic-gate }; 1436*7c478bd9Sstevel@tonic-gate }; 1437*7c478bd9Sstevel@tonic-gate }; 1438*7c478bd9Sstevel@tonic-gate /* 1439*7c478bd9Sstevel@tonic-gate * Start a new line. 1440*7c478bd9Sstevel@tonic-gate */ 1441*7c478bd9Sstevel@tonic-gate { 1442*7c478bd9Sstevel@tonic-gate char s[] = "\r\n"; 1443*7c478bd9Sstevel@tonic-gate int n = strlen(s); 1444*7c478bd9Sstevel@tonic-gate if(write_fn(data, s, n) != n) 1445*7c478bd9Sstevel@tonic-gate return 1; 1446*7c478bd9Sstevel@tonic-gate }; 1447*7c478bd9Sstevel@tonic-gate return 0; 1448*7c478bd9Sstevel@tonic-gate } 1449*7c478bd9Sstevel@tonic-gate 1450*7c478bd9Sstevel@tonic-gate #endif /* ifndef WITHOUT_FILE_SYSTEM */ 1451