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