1 /* -*- Mode: C -*- */ 2 3 /* pathfind.c --- find a FILE MODE along PATH */ 4 5 /* Author: Gary V Vaughan <gvaughan@oranda.demon.co.uk> */ 6 7 /* Code: */ 8 9 static char * 10 pathfind( char const * path, 11 char const * fname, 12 char const * mode ); 13 14 #include "compat.h" 15 #ifndef HAVE_PATHFIND 16 #if defined(__windows__) && !defined(__CYGWIN__) 17 static char * 18 pathfind( char const * path, 19 char const * fname, 20 char const * mode ) 21 { 22 return strdup(fname); 23 } 24 #else 25 26 static char * make_absolute(char const * string, char const * dot_path); 27 static char * canonicalize_pathname(char * path); 28 static char * extract_colon_unit(char * dir, char const * string, int * p_index); 29 30 /** 31 * local implementation of pathfind. 32 * @param[in] path colon separated list of directories 33 * @param[in] fname the name we are hunting for 34 * @param[in] mode the required file mode 35 * @returns an allocated string with the full path, or NULL 36 */ 37 static char * 38 pathfind( char const * path, 39 char const * fname, 40 char const * mode ) 41 { 42 int p_index = 0; 43 int mode_bits = 0; 44 char * res_path = NULL; 45 char zPath[ AG_PATH_MAX + 1 ]; 46 47 if (strchr( mode, 'r' )) mode_bits |= R_OK; 48 if (strchr( mode, 'w' )) mode_bits |= W_OK; 49 if (strchr( mode, 'x' )) mode_bits |= X_OK; 50 51 /* 52 * FOR each non-null entry in the colon-separated path, DO ... 53 */ 54 for (;;) { 55 DIR * dirP; 56 char * colon_unit = extract_colon_unit( zPath, path, &p_index ); 57 58 if (colon_unit == NULL) 59 break; 60 61 dirP = opendir( colon_unit ); 62 63 /* 64 * IF the directory is inaccessable, THEN next directory 65 */ 66 if (dirP == NULL) 67 continue; 68 69 for (;;) { 70 struct dirent *entP = readdir( dirP ); 71 72 if (entP == (struct dirent *)NULL) 73 break; 74 75 /* 76 * IF the file name matches the one we are looking for, ... 77 */ 78 if (strcmp(entP->d_name, fname) == 0) { 79 char * abs_name = make_absolute(fname, colon_unit); 80 81 /* 82 * Make sure we can access it in the way we want 83 */ 84 if (access(abs_name, mode_bits) >= 0) { 85 /* 86 * We can, so normalize the name and return it below 87 */ 88 res_path = canonicalize_pathname(abs_name); 89 } 90 91 free(abs_name); 92 break; 93 } 94 } 95 96 closedir( dirP ); 97 98 if (res_path != NULL) 99 break; 100 } 101 102 return res_path; 103 } 104 105 /* 106 * Turn STRING (a pathname) into an absolute pathname, assuming that 107 * DOT_PATH contains the symbolic location of `.'. This always returns 108 * a new string, even if STRING was an absolute pathname to begin with. 109 */ 110 static char * 111 make_absolute( char const * string, char const * dot_path ) 112 { 113 char * result; 114 int result_len; 115 116 if (!dot_path || *string == '/') { 117 result = strdup( string ); 118 if (result == NULL) { 119 return NULL; /* couldn't allocate memory */ 120 } 121 } else { 122 if (dot_path && dot_path[0]) { 123 result = malloc( 2 + strlen( dot_path ) + strlen( string ) ); 124 if (result == NULL) { 125 return NULL; /* couldn't allocate memory */ 126 } 127 strcpy( result, dot_path ); 128 result_len = (int)strlen(result); 129 if (result[result_len - 1] != '/') { 130 result[result_len++] = '/'; 131 result[result_len] = '\0'; 132 } 133 } else { 134 result = malloc( 3 + strlen( string ) ); 135 if (result == NULL) { 136 return NULL; /* couldn't allocate memory */ 137 } 138 result[0] = '.'; result[1] = '/'; result[2] = '\0'; 139 result_len = 2; 140 } 141 142 strcpy( result + result_len, string ); 143 } 144 145 return result; 146 } 147 148 /* 149 * Canonicalize PATH, and return a new path. The new path differs from 150 * PATH in that: 151 * 152 * Multiple `/'s are collapsed to a single `/'. 153 * Leading `./'s are removed. 154 * Trailing `/.'s are removed. 155 * Trailing `/'s are removed. 156 * Non-leading `../'s and trailing `..'s are handled by removing 157 * portions of the path. 158 */ 159 static char * 160 canonicalize_pathname( char *path ) 161 { 162 int i, start; 163 char stub_char, *result; 164 165 /* The result cannot be larger than the input PATH. */ 166 result = strdup( path ); 167 if (result == NULL) { 168 return NULL; /* couldn't allocate memory */ 169 } 170 stub_char = (*path == '/') ? '/' : '.'; 171 172 /* Walk along RESULT looking for things to compact. */ 173 i = 0; 174 while (result[i]) { 175 while (result[i] != '\0' && result[i] != '/') 176 i++; 177 178 start = i++; 179 180 /* If we didn't find any slashes, then there is nothing left to 181 * do. 182 */ 183 if (!result[start]) 184 break; 185 186 /* Handle multiple `/'s in a row. */ 187 while (result[i] == '/') 188 i++; 189 190 #if !defined (apollo) 191 if ((start + 1) != i) 192 #else 193 if ((start + 1) != i && (start != 0 || i != 2)) 194 #endif /* apollo */ 195 { 196 strcpy( result + start + 1, result + i ); 197 i = start + 1; 198 } 199 200 /* Handle backquoted `/'. */ 201 if (start > 0 && result[start - 1] == '\\') 202 continue; 203 204 /* Check for trailing `/', and `.' by itself. */ 205 if ((start && !result[i]) 206 || (result[i] == '.' && !result[i+1])) { 207 result[--i] = '\0'; 208 break; 209 } 210 211 /* Check for `../', `./' or trailing `.' by itself. */ 212 if (result[i] == '.') { 213 /* Handle `./'. */ 214 if (result[i + 1] == '/') { 215 strcpy( result + i, result + i + 1 ); 216 i = (start < 0) ? 0 : start; 217 continue; 218 } 219 220 /* Handle `../' or trailing `..' by itself. */ 221 if (result[i + 1] == '.' && 222 (result[i + 2] == '/' || !result[i + 2])) { 223 while (--start > -1 && result[start] != '/') 224 ; 225 strcpy( result + start + 1, result + i + 2 ); 226 i = (start < 0) ? 0 : start; 227 continue; 228 } 229 } 230 } 231 232 if (!*result) { 233 *result = stub_char; 234 result[1] = '\0'; 235 } 236 237 return result; 238 } 239 240 /* 241 * Given a string containing units of information separated by colons, 242 * return the next one pointed to by (P_INDEX), or NULL if there are no 243 * more. Advance (P_INDEX) to the character after the colon. 244 */ 245 static char * 246 extract_colon_unit(char * pzDir, char const * string, int * p_index) 247 { 248 char * pzDest = pzDir; 249 int ix = *p_index; 250 251 if (string == NULL) 252 return NULL; 253 254 if ((unsigned)ix >= strlen( string )) 255 return NULL; 256 257 { 258 char const * pzSrc = string + ix; 259 260 while (*pzSrc == ':') pzSrc++; 261 262 for (;;) { 263 char ch = (*(pzDest++) = *(pzSrc++)); 264 switch (ch) { 265 case ':': 266 pzDest[-1] = NUL; 267 /* FALLTHROUGH */ 268 case NUL: 269 goto copy_done; 270 } 271 272 if ((unsigned long)(pzDest - pzDir) >= AG_PATH_MAX) 273 break; 274 } copy_done:; 275 276 ix = (int)(pzSrc - string); 277 } 278 279 if (*pzDir == NUL) 280 return NULL; 281 282 *p_index = ix; 283 return pzDir; 284 } 285 #endif /* __windows__ / __CYGWIN__ */ 286 #endif /* HAVE_PATHFIND */ 287 288 /* 289 * Local Variables: 290 * mode: C 291 * c-file-style: "stroustrup" 292 * indent-tabs-mode: nil 293 * End: 294 * end of compat/pathfind.c */ 295