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