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 } else { 119 if (dot_path && dot_path[0]) { 120 result = malloc( 2 + strlen( dot_path ) + strlen( string ) ); 121 strcpy( result, dot_path ); 122 result_len = (int)strlen(result); 123 if (result[result_len - 1] != '/') { 124 result[result_len++] = '/'; 125 result[result_len] = '\0'; 126 } 127 } else { 128 result = malloc( 3 + strlen( string ) ); 129 result[0] = '.'; result[1] = '/'; result[2] = '\0'; 130 result_len = 2; 131 } 132 133 strcpy( result + result_len, string ); 134 } 135 136 return result; 137 } 138 139 /* 140 * Canonicalize PATH, and return a new path. The new path differs from 141 * PATH in that: 142 * 143 * Multiple `/'s are collapsed to a single `/'. 144 * Leading `./'s are removed. 145 * Trailing `/.'s are removed. 146 * Trailing `/'s are removed. 147 * Non-leading `../'s and trailing `..'s are handled by removing 148 * portions of the path. 149 */ 150 static char * 151 canonicalize_pathname( char *path ) 152 { 153 int i, start; 154 char stub_char, *result; 155 156 /* The result cannot be larger than the input PATH. */ 157 result = strdup( path ); 158 159 stub_char = (*path == '/') ? '/' : '.'; 160 161 /* Walk along RESULT looking for things to compact. */ 162 i = 0; 163 while (result[i]) { 164 while (result[i] != '\0' && result[i] != '/') 165 i++; 166 167 start = i++; 168 169 /* If we didn't find any slashes, then there is nothing left to 170 * do. 171 */ 172 if (!result[start]) 173 break; 174 175 /* Handle multiple `/'s in a row. */ 176 while (result[i] == '/') 177 i++; 178 179 #if !defined (apollo) 180 if ((start + 1) != i) 181 #else 182 if ((start + 1) != i && (start != 0 || i != 2)) 183 #endif /* apollo */ 184 { 185 strcpy( result + start + 1, result + i ); 186 i = start + 1; 187 } 188 189 /* Handle backquoted `/'. */ 190 if (start > 0 && result[start - 1] == '\\') 191 continue; 192 193 /* Check for trailing `/', and `.' by itself. */ 194 if ((start && !result[i]) 195 || (result[i] == '.' && !result[i+1])) { 196 result[--i] = '\0'; 197 break; 198 } 199 200 /* Check for `../', `./' or trailing `.' by itself. */ 201 if (result[i] == '.') { 202 /* Handle `./'. */ 203 if (result[i + 1] == '/') { 204 strcpy( result + i, result + i + 1 ); 205 i = (start < 0) ? 0 : start; 206 continue; 207 } 208 209 /* Handle `../' or trailing `..' by itself. */ 210 if (result[i + 1] == '.' && 211 (result[i + 2] == '/' || !result[i + 2])) { 212 while (--start > -1 && result[start] != '/') 213 ; 214 strcpy( result + start + 1, result + i + 2 ); 215 i = (start < 0) ? 0 : start; 216 continue; 217 } 218 } 219 } 220 221 if (!*result) { 222 *result = stub_char; 223 result[1] = '\0'; 224 } 225 226 return result; 227 } 228 229 /* 230 * Given a string containing units of information separated by colons, 231 * return the next one pointed to by (P_INDEX), or NULL if there are no 232 * more. Advance (P_INDEX) to the character after the colon. 233 */ 234 static char * 235 extract_colon_unit(char * pzDir, char const * string, int * p_index) 236 { 237 char * pzDest = pzDir; 238 int ix = *p_index; 239 240 if (string == NULL) 241 return NULL; 242 243 if ((unsigned)ix >= strlen( string )) 244 return NULL; 245 246 { 247 char const * pzSrc = string + ix; 248 249 while (*pzSrc == ':') pzSrc++; 250 251 for (;;) { 252 char ch = (*(pzDest++) = *(pzSrc++)); 253 switch (ch) { 254 case ':': 255 pzDest[-1] = NUL; 256 /* FALLTHROUGH */ 257 case NUL: 258 goto copy_done; 259 } 260 261 if ((unsigned long)(pzDest - pzDir) >= AG_PATH_MAX) 262 break; 263 } copy_done:; 264 265 ix = (int)(pzSrc - string); 266 } 267 268 if (*pzDir == NUL) 269 return NULL; 270 271 *p_index = ix; 272 return pzDir; 273 } 274 #endif /* __windows__ / __CYGWIN__ */ 275 #endif /* HAVE_PATHFIND */ 276 277 /* 278 * Local Variables: 279 * mode: C 280 * c-file-style: "stroustrup" 281 * indent-tabs-mode: nil 282 * End: 283 * end of compat/pathfind.c */ 284