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