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