pathfind.c 8.9 KB

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