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