pathfind.c 7.2 KB

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