pathfind.c 9.1 KB

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