1/* -*- Mode: C -*- */ 2 3/* pathfind.c --- find a FILE MODE along PATH */ 4 5/* 6 * Author: Gary V Vaughan <gvaughan@oranda.demon.co.uk> 7 * Time-stamp: "2006-09-23 19:46:16 bkorb" 8 * by: bkorb 9 * 10 * $Id: 8ce7ddfe2378f0b75c91c0ab348a6ad81634fb01 $ 11 */ 12 13/* Code: */ 14 15#include "compat.h" 16#ifndef HAVE_PATHFIND 17#if defined(__windows__) && !defined(__CYGWIN__) 18char* 19pathfind( char const* path, 20 char const* fileName, 21 char const* mode ) 22{ 23 return NULL; 24} 25#else 26 27static char* make_absolute( char const *string, char const *dot_path ); 28static char* canonicalize_pathname( char *path ); 29static char* extract_colon_unit( char* dir, char const *string, int *p_index ); 30 31 32/*=export_func pathfind 33 * 34 * what: fild a file in a list of directories 35 * 36 * ifndef: HAVE_PATHFIND 37 * 38 * arg: + char const* + path + colon separated list of search directories + 39 * arg: + char const* + file + the name of the file to look for + 40 * arg: + char const* + mode + the mode bits that must be set to match + 41 * 42 * ret_type: char* 43 * ret_desc: the path to the located file 44 * 45 * doc: 46 * 47 * pathfind looks for a a file with name "FILE" and "MODE" access 48 * along colon delimited "PATH", and returns the full pathname as a 49 * string, or NULL if not found. If "FILE" contains a slash, then 50 * it is treated as a relative or absolute path and "PATH" is ignored. 51 * 52 * @strong{NOTE}: this function is compiled into @file{libopts} only if 53 * it is not natively supplied. 54 * 55 * The "MODE" argument is a string of option letters chosen from the 56 * list below: 57 * @example 58 * Letter Meaning 59 * r readable 60 * w writable 61 * x executable 62 * f normal file (NOT IMPLEMENTED) 63 * b block special (NOT IMPLEMENTED) 64 * c character special (NOT IMPLEMENTED) 65 * d directory (NOT IMPLEMENTED) 66 * p FIFO (pipe) (NOT IMPLEMENTED) 67 * u set user ID bit (NOT IMPLEMENTED) 68 * g set group ID bit (NOT IMPLEMENTED) 69 * k sticky bit (NOT IMPLEMENTED) 70 * s size nonzero (NOT IMPLEMENTED) 71 * @end example 72 * 73 * example: 74 * To find the "ls" command using the "PATH" environment variable: 75 * @example 76 * #include <stdlib.h> 77 * char* pz_ls = pathfind( getenv("PATH"), "ls", "rx" ); 78 * <<do whatever with pz_ls>> 79 * free( pz_ls ); 80 * @end example 81 * The path is allocated with @code{malloc(3C)}, so you must @code{free(3C)} 82 * the result. Also, do not use unimplemented file modes. :-) 83 * 84 * err: returns NULL if the file is not found. 85=*/ 86char* 87pathfind( char const* path, 88 char const* fileName, 89 char const* mode ) 90{ 91 int p_index = 0; 92 int mode_bits = 0; 93 char* pathName = NULL; 94 char zPath[ AG_PATH_MAX + 1 ]; 95 96 if (strchr( mode, 'r' )) mode_bits |= R_OK; 97 if (strchr( mode, 'w' )) mode_bits |= W_OK; 98 if (strchr( mode, 'x' )) mode_bits |= X_OK; 99 100 /* 101 * FOR each non-null entry in the colon-separated path, DO ... 102 */ 103 for (;;) { 104 DIR* dirP; 105 char* colon_unit = extract_colon_unit( zPath, path, &p_index ); 106 107 /* 108 * IF no more entries, THEN quit 109 */ 110 if (colon_unit == NULL) 111 break; 112 113 dirP = opendir( colon_unit ); 114 115 /* 116 * IF the directory is inaccessable, THEN next directory 117 */ 118 if (dirP == NULL) 119 continue; 120 121 /* 122 * FOR every entry in the given directory, ... 123 */ 124 for (;;) { 125 struct dirent *entP = readdir( dirP ); 126 127 if (entP == (struct dirent*)NULL) 128 break; 129 130 /* 131 * IF the file name matches the one we are looking for, ... 132 */ 133 if (strcmp( entP->d_name, fileName ) == 0) { 134 char* pzFullName = make_absolute( fileName, colon_unit); 135 136 /* 137 * Make sure we can access it in the way we want 138 */ 139 if (access( pzFullName, mode_bits ) >= 0) { 140 /* 141 * We can, so normalize the name and return it below 142 */ 143 pathName = canonicalize_pathname( pzFullName ); 144 } 145 146 free( (void*)pzFullName ); 147 break; 148 } 149 } 150 151 closedir( dirP ); 152 153 if (pathName != NULL) 154 break; 155 } 156 157 return pathName; 158} 159 160/* 161 * Turn STRING (a pathname) into an absolute pathname, assuming that 162 * DOT_PATH contains the symbolic location of `.'. This always returns 163 * a new string, even if STRING was an absolute pathname to begin with. 164 */ 165static char* 166make_absolute( char const *string, char const *dot_path ) 167{ 168 char *result; 169 int result_len; 170 171 if (!dot_path || *string == '/') { 172 result = strdup( string ); 173 } else { 174 if (dot_path && dot_path[0]) { 175 result = malloc( 2 + strlen( dot_path ) + strlen( string ) ); 176 strcpy( result, dot_path ); 177 result_len = strlen( result ); 178 if (result[result_len - 1] != '/') { 179 result[result_len++] = '/'; 180 result[result_len] = '\0'; 181 } 182 } else { 183 result = malloc( 3 + strlen( string ) ); 184 result[0] = '.'; result[1] = '/'; result[2] = '\0'; 185 result_len = 2; 186 } 187 188 strcpy( result + result_len, string ); 189 } 190 191 return result; 192} 193 194/* 195 * Canonicalize PATH, and return a new path. The new path differs from 196 * PATH in that: 197 * 198 * Multiple `/'s are collapsed to a single `/'. 199 * Leading `./'s are removed. 200 * Trailing `/.'s are removed. 201 * Trailing `/'s are removed. 202 * Non-leading `../'s and trailing `..'s are handled by removing 203 * portions of the path. 204 */ 205static char* 206canonicalize_pathname( char *path ) 207{ 208 int i, start; 209 char stub_char, *result; 210 211 /* The result cannot be larger than the input PATH. */ 212 result = strdup( path ); 213 214 stub_char = (*path == '/') ? '/' : '.'; 215 216 /* Walk along RESULT looking for things to compact. */ 217 i = 0; 218 while (result[i]) { 219 while (result[i] != '\0' && result[i] != '/') 220 i++; 221 222 start = i++; 223 224 /* If we didn't find any slashes, then there is nothing left to 225 * do. 226 */ 227 if (!result[start]) 228 break; 229 230 /* Handle multiple `/'s in a row. */ 231 while (result[i] == '/') 232 i++; 233 234#if !defined (apollo) 235 if ((start + 1) != i) 236#else 237 if ((start + 1) != i && (start != 0 || i != 2)) 238#endif /* apollo */ 239 { 240 strcpy( result + start + 1, result + i ); 241 i = start + 1; 242 } 243 244 /* Handle backquoted `/'. */ 245 if (start > 0 && result[start - 1] == '\\') 246 continue; 247 248 /* Check for trailing `/', and `.' by itself. */ 249 if ((start && !result[i]) 250 || (result[i] == '.' && !result[i+1])) { 251 result[--i] = '\0'; 252 break; 253 } 254 255 /* Check for `../', `./' or trailing `.' by itself. */ 256 if (result[i] == '.') { 257 /* Handle `./'. */ 258 if (result[i + 1] == '/') { 259 strcpy( result + i, result + i + 1 ); 260 i = (start < 0) ? 0 : start; 261 continue; 262 } 263 264 /* Handle `../' or trailing `..' by itself. */ 265 if (result[i + 1] == '.' && 266 (result[i + 2] == '/' || !result[i + 2])) { 267 while (--start > -1 && result[start] != '/') 268 ; 269 strcpy( result + start + 1, result + i + 2 ); 270 i = (start < 0) ? 0 : start; 271 continue; 272 } 273 } 274 } 275 276 if (!*result) { 277 *result = stub_char; 278 result[1] = '\0'; 279 } 280 281 return result; 282} 283 284/* 285 * Given a string containing units of information separated by colons, 286 * return the next one pointed to by (P_INDEX), or NULL if there are no 287 * more. Advance (P_INDEX) to the character after the colon. 288 */ 289static char* 290extract_colon_unit( char* pzDir, char const *string, int *p_index ) 291{ 292 char* pzDest = pzDir; 293 int ix = *p_index; 294 295 if (string == NULL) 296 return NULL; 297 298 if ((unsigned)ix >= strlen( string )) 299 return NULL; 300 301 { 302 char const* pzSrc = string + ix; 303 304 while (*pzSrc == ':') pzSrc++; 305 306 for (;;) { 307 char ch = (*(pzDest++) = *(pzSrc++)); 308 switch (ch) { 309 case ':': 310 pzDest[-1] = NUL; 311 case NUL: 312 goto copy_done; 313 } 314 315 if ((pzDest - pzDir) >= AG_PATH_MAX) 316 break; 317 } copy_done:; 318 319 ix = pzSrc - string; 320 } 321 322 if (*pzDir == NUL) 323 return NULL; 324 325 *p_index = ix; 326 return pzDir; 327} 328#endif /* __windows__ / __CYGWIN__ */ 329#endif /* HAVE_PATHFIND */ 330 331/* 332 * Local Variables: 333 * mode: C 334 * c-file-style: "stroustrup" 335 * indent-tabs-mode: nil 336 * End: 337 * end of compat/pathfind.c */ 338