bumped version
[gnutls.git] / src / libopts / compat / pathfind.c
blob2e6f881ec931036b9d8517c4ab8e2dc79dd3eca2
1 /* -*- Mode: C -*- */
3 /* pathfind.c --- find a FILE MODE along PATH */
5 /*
6 * Author: Gary V Vaughan <gvaughan@oranda.demon.co.uk>
7 * Time-stamp: "2012-03-31 13:44:42 bkorb"
8 */
10 /* Code: */
12 #include "compat.h"
13 #ifndef HAVE_PATHFIND
14 #if defined(__windows__) && !defined(__CYGWIN__)
15 char*
16 pathfind( char const* path,
17 char const* fileName,
18 char const* mode )
20 return NULL;
22 #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 );
29 /*=export_func pathfind
31 * what: fild a file in a list of directories
33 * ifndef: HAVE_PATHFIND
35 * arg: + char const* + path + colon separated list of search directories +
36 * arg: + char const* + file + the name of the file to look for +
37 * arg: + char const* + mode + the mode bits that must be set to match +
39 * ret_type: char*
40 * ret_desc: the path to the located file
42 * doc:
44 * pathfind looks for a a file with name "FILE" and "MODE" access
45 * along colon delimited "PATH", and returns the full pathname as a
46 * string, or NULL if not found. If "FILE" contains a slash, then
47 * it is treated as a relative or absolute path and "PATH" is ignored.
49 * @strong{NOTE}: this function is compiled into @file{libopts} only if
50 * it is not natively supplied.
52 * The "MODE" argument is a string of option letters chosen from the
53 * list below:
54 * @example
55 * Letter Meaning
56 * r readable
57 * w writable
58 * x executable
59 * f normal file (NOT IMPLEMENTED)
60 * b block special (NOT IMPLEMENTED)
61 * c character special (NOT IMPLEMENTED)
62 * d directory (NOT IMPLEMENTED)
63 * p FIFO (pipe) (NOT IMPLEMENTED)
64 * u set user ID bit (NOT IMPLEMENTED)
65 * g set group ID bit (NOT IMPLEMENTED)
66 * k sticky bit (NOT IMPLEMENTED)
67 * s size nonzero (NOT IMPLEMENTED)
68 * @end example
70 * example:
71 * To find the "ls" command using the "PATH" environment variable:
72 * @example
73 * #include <stdlib.h>
74 * char* pz_ls = pathfind( getenv("PATH"), "ls", "rx" );
75 * <<do whatever with pz_ls>>
76 * free( pz_ls );
77 * @end example
78 * The path is allocated with @code{malloc(3C)}, so you must @code{free(3C)}
79 * the result. Also, do not use unimplemented file modes. :-)
81 * err: returns NULL if the file is not found.
82 =*/
83 char*
84 pathfind( char const* path,
85 char const* fileName,
86 char const* mode )
88 int p_index = 0;
89 int mode_bits = 0;
90 char* pathName = NULL;
91 char zPath[ AG_PATH_MAX + 1 ];
93 if (strchr( mode, 'r' )) mode_bits |= R_OK;
94 if (strchr( mode, 'w' )) mode_bits |= W_OK;
95 if (strchr( mode, 'x' )) mode_bits |= X_OK;
98 * FOR each non-null entry in the colon-separated path, DO ...
100 for (;;) {
101 DIR* dirP;
102 char* colon_unit = extract_colon_unit( zPath, path, &p_index );
105 * IF no more entries, THEN quit
107 if (colon_unit == NULL)
108 break;
110 dirP = opendir( colon_unit );
113 * IF the directory is inaccessable, THEN next directory
115 if (dirP == NULL)
116 continue;
119 * FOR every entry in the given directory, ...
121 for (;;) {
122 struct dirent *entP = readdir( dirP );
124 if (entP == (struct dirent*)NULL)
125 break;
128 * IF the file name matches the one we are looking for, ...
130 if (strcmp( entP->d_name, fileName ) == 0) {
131 char* pzFullName = make_absolute( fileName, colon_unit);
134 * Make sure we can access it in the way we want
136 if (access( pzFullName, mode_bits ) >= 0) {
138 * We can, so normalize the name and return it below
140 pathName = canonicalize_pathname( pzFullName );
143 free( (void*)pzFullName );
144 break;
148 closedir( dirP );
150 if (pathName != NULL)
151 break;
154 return pathName;
158 * Turn STRING (a pathname) into an absolute pathname, assuming that
159 * DOT_PATH contains the symbolic location of `.'. This always returns
160 * a new string, even if STRING was an absolute pathname to begin with.
162 static char*
163 make_absolute( char const *string, char const *dot_path )
165 char *result;
166 int result_len;
168 if (!dot_path || *string == '/') {
169 result = strdup( string );
170 } else {
171 if (dot_path && dot_path[0]) {
172 result = malloc( 2 + strlen( dot_path ) + strlen( string ) );
173 strcpy( result, dot_path );
174 result_len = strlen( result );
175 if (result[result_len - 1] != '/') {
176 result[result_len++] = '/';
177 result[result_len] = '\0';
179 } else {
180 result = malloc( 3 + strlen( string ) );
181 result[0] = '.'; result[1] = '/'; result[2] = '\0';
182 result_len = 2;
185 strcpy( result + result_len, string );
188 return result;
192 * Canonicalize PATH, and return a new path. The new path differs from
193 * PATH in that:
195 * Multiple `/'s are collapsed to a single `/'.
196 * Leading `./'s are removed.
197 * Trailing `/.'s are removed.
198 * Trailing `/'s are removed.
199 * Non-leading `../'s and trailing `..'s are handled by removing
200 * portions of the path.
202 static char*
203 canonicalize_pathname( char *path )
205 int i, start;
206 char stub_char, *result;
208 /* The result cannot be larger than the input PATH. */
209 result = strdup( path );
211 stub_char = (*path == '/') ? '/' : '.';
213 /* Walk along RESULT looking for things to compact. */
214 i = 0;
215 while (result[i]) {
216 while (result[i] != '\0' && result[i] != '/')
217 i++;
219 start = i++;
221 /* If we didn't find any slashes, then there is nothing left to
222 * do.
224 if (!result[start])
225 break;
227 /* Handle multiple `/'s in a row. */
228 while (result[i] == '/')
229 i++;
231 #if !defined (apollo)
232 if ((start + 1) != i)
233 #else
234 if ((start + 1) != i && (start != 0 || i != 2))
235 #endif /* apollo */
237 strcpy( result + start + 1, result + i );
238 i = start + 1;
241 /* Handle backquoted `/'. */
242 if (start > 0 && result[start - 1] == '\\')
243 continue;
245 /* Check for trailing `/', and `.' by itself. */
246 if ((start && !result[i])
247 || (result[i] == '.' && !result[i+1])) {
248 result[--i] = '\0';
249 break;
252 /* Check for `../', `./' or trailing `.' by itself. */
253 if (result[i] == '.') {
254 /* Handle `./'. */
255 if (result[i + 1] == '/') {
256 strcpy( result + i, result + i + 1 );
257 i = (start < 0) ? 0 : start;
258 continue;
261 /* Handle `../' or trailing `..' by itself. */
262 if (result[i + 1] == '.' &&
263 (result[i + 2] == '/' || !result[i + 2])) {
264 while (--start > -1 && result[start] != '/')
266 strcpy( result + start + 1, result + i + 2 );
267 i = (start < 0) ? 0 : start;
268 continue;
273 if (!*result) {
274 *result = stub_char;
275 result[1] = '\0';
278 return result;
282 * Given a string containing units of information separated by colons,
283 * return the next one pointed to by (P_INDEX), or NULL if there are no
284 * more. Advance (P_INDEX) to the character after the colon.
286 static char*
287 extract_colon_unit( char* pzDir, char const *string, int *p_index )
289 char* pzDest = pzDir;
290 int ix = *p_index;
292 if (string == NULL)
293 return NULL;
295 if ((unsigned)ix >= strlen( string ))
296 return NULL;
299 char const* pzSrc = string + ix;
301 while (*pzSrc == ':') pzSrc++;
303 for (;;) {
304 char ch = (*(pzDest++) = *(pzSrc++));
305 switch (ch) {
306 case ':':
307 pzDest[-1] = NUL;
308 case NUL:
309 goto copy_done;
312 if ((unsigned long)(pzDest - pzDir) >= AG_PATH_MAX)
313 break;
314 } copy_done:;
316 ix = pzSrc - string;
319 if (*pzDir == NUL)
320 return NULL;
322 *p_index = ix;
323 return pzDir;
325 #endif /* __windows__ / __CYGWIN__ */
326 #endif /* HAVE_PATHFIND */
329 * Local Variables:
330 * mode: C
331 * c-file-style: "stroustrup"
332 * indent-tabs-mode: nil
333 * End:
334 * end of compat/pathfind.c */