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