1181834Sroberto/*  -*- Mode: C -*-  */
2181834Sroberto
3181834Sroberto/* pathfind.c --- find a FILE  MODE along PATH */
4181834Sroberto
5285612Sdelphij/* Author: Gary V Vaughan <gvaughan@oranda.demon.co.uk> */
6181834Sroberto
7181834Sroberto/* Code: */
8181834Sroberto
9285612Sdelphijstatic char *
10285612Sdelphijpathfind( char const * path,
11285612Sdelphij          char const * fname,
12285612Sdelphij          char const * mode );
13285612Sdelphij
14181834Sroberto#include "compat.h"
15181834Sroberto#ifndef HAVE_PATHFIND
16181834Sroberto#if defined(__windows__) && !defined(__CYGWIN__)
17285612Sdelphijstatic char *
18285612Sdelphijpathfind( char const * path,
19285612Sdelphij          char const * fname,
20285612Sdelphij          char const * mode )
21181834Sroberto{
22285612Sdelphij    return strdup(fname);
23181834Sroberto}
24181834Sroberto#else
25181834Sroberto
26285612Sdelphijstatic char * make_absolute(char const * string, char const * dot_path);
27285612Sdelphijstatic char * canonicalize_pathname(char * path);
28285612Sdelphijstatic char * extract_colon_unit(char * dir, char const * string, int * p_index);
29181834Sroberto
30285612Sdelphij/**
31285612Sdelphij * local implementation of pathfind.
32285612Sdelphij * @param[in] path  colon separated list of directories
33285612Sdelphij * @param[in] fname the name we are hunting for
34285612Sdelphij * @param[in] mode  the required file mode
35285612Sdelphij * @returns an allocated string with the full path, or NULL
36285612Sdelphij */
37285612Sdelphijstatic char *
38285612Sdelphijpathfind( char const * path,
39285612Sdelphij          char const * fname,
40285612Sdelphij          char const * mode )
41181834Sroberto{
42285612Sdelphij    int    p_index   = 0;
43285612Sdelphij    int    mode_bits = 0;
44285612Sdelphij    char * res_path  = NULL;
45285612Sdelphij    char   zPath[ AG_PATH_MAX + 1 ];
46181834Sroberto
47181834Sroberto    if (strchr( mode, 'r' )) mode_bits |= R_OK;
48181834Sroberto    if (strchr( mode, 'w' )) mode_bits |= W_OK;
49181834Sroberto    if (strchr( mode, 'x' )) mode_bits |= X_OK;
50181834Sroberto
51181834Sroberto    /*
52181834Sroberto     *  FOR each non-null entry in the colon-separated path, DO ...
53181834Sroberto     */
54181834Sroberto    for (;;) {
55285612Sdelphij        DIR  * dirP;
56285612Sdelphij        char * colon_unit = extract_colon_unit( zPath, path, &p_index );
57181834Sroberto
58181834Sroberto        if (colon_unit == NULL)
59181834Sroberto            break;
60181834Sroberto
61181834Sroberto        dirP = opendir( colon_unit );
62181834Sroberto
63181834Sroberto        /*
64181834Sroberto         *  IF the directory is inaccessable, THEN next directory
65181834Sroberto         */
66181834Sroberto        if (dirP == NULL)
67181834Sroberto            continue;
68181834Sroberto
69181834Sroberto        for (;;) {
70181834Sroberto            struct dirent *entP = readdir( dirP );
71181834Sroberto
72285612Sdelphij            if (entP == (struct dirent *)NULL)
73181834Sroberto                break;
74181834Sroberto
75181834Sroberto            /*
76181834Sroberto             *  IF the file name matches the one we are looking for, ...
77181834Sroberto             */
78285612Sdelphij            if (strcmp(entP->d_name, fname) == 0) {
79285612Sdelphij                char * abs_name = make_absolute(fname, colon_unit);
80181834Sroberto
81181834Sroberto                /*
82181834Sroberto                 *  Make sure we can access it in the way we want
83181834Sroberto                 */
84285612Sdelphij                if (access(abs_name, mode_bits) >= 0) {
85181834Sroberto                    /*
86181834Sroberto                     *  We can, so normalize the name and return it below
87181834Sroberto                     */
88285612Sdelphij                    res_path = canonicalize_pathname(abs_name);
89181834Sroberto                }
90181834Sroberto
91285612Sdelphij                free(abs_name);
92181834Sroberto                break;
93181834Sroberto            }
94181834Sroberto        }
95181834Sroberto
96181834Sroberto        closedir( dirP );
97181834Sroberto
98285612Sdelphij        if (res_path != NULL)
99181834Sroberto            break;
100181834Sroberto    }
101181834Sroberto
102285612Sdelphij    return res_path;
103181834Sroberto}
104181834Sroberto
105181834Sroberto/*
106181834Sroberto * Turn STRING  (a pathname) into an  absolute  pathname, assuming  that
107181834Sroberto * DOT_PATH contains the symbolic location of  `.'.  This always returns
108181834Sroberto * a new string, even if STRING was an absolute pathname to begin with.
109181834Sroberto */
110285612Sdelphijstatic char *
111285612Sdelphijmake_absolute( char const * string, char const * dot_path )
112181834Sroberto{
113285612Sdelphij    char * result;
114181834Sroberto    int result_len;
115181834Sroberto
116181834Sroberto    if (!dot_path || *string == '/') {
117181834Sroberto        result = strdup( string );
118289997Sglebius	if (result == NULL) {
119289997Sglebius	return NULL;    /* couldn't allocate memory    */
120289997Sglebius	}
121181834Sroberto    } else {
122181834Sroberto        if (dot_path && dot_path[0]) {
123181834Sroberto            result = malloc( 2 + strlen( dot_path ) + strlen( string ) );
124289997Sglebius		if (result == NULL) {
125289997Sglebius		return NULL;    /* couldn't allocate memory    */
126289997Sglebius		}
127181834Sroberto            strcpy( result, dot_path );
128285612Sdelphij            result_len = (int)strlen(result);
129181834Sroberto            if (result[result_len - 1] != '/') {
130181834Sroberto                result[result_len++] = '/';
131181834Sroberto                result[result_len] = '\0';
132181834Sroberto            }
133181834Sroberto        } else {
134181834Sroberto            result = malloc( 3 + strlen( string ) );
135289997Sglebius		if (result == NULL) {
136289997Sglebius		return NULL;    /* couldn't allocate memory    */
137289997Sglebius		}
138181834Sroberto            result[0] = '.'; result[1] = '/'; result[2] = '\0';
139181834Sroberto            result_len = 2;
140181834Sroberto        }
141181834Sroberto
142181834Sroberto        strcpy( result + result_len, string );
143181834Sroberto    }
144181834Sroberto
145181834Sroberto    return result;
146181834Sroberto}
147181834Sroberto
148181834Sroberto/*
149181834Sroberto * Canonicalize PATH, and return a  new path.  The new path differs from
150181834Sroberto * PATH in that:
151181834Sroberto *
152181834Sroberto *    Multiple `/'s     are collapsed to a single `/'.
153181834Sroberto *    Leading `./'s     are removed.
154181834Sroberto *    Trailing `/.'s    are removed.
155181834Sroberto *    Trailing `/'s     are removed.
156181834Sroberto *    Non-leading `../'s and trailing `..'s are handled by removing
157181834Sroberto *                    portions of the path.
158181834Sroberto */
159285612Sdelphijstatic char *
160181834Srobertocanonicalize_pathname( char *path )
161181834Sroberto{
162181834Sroberto    int i, start;
163181834Sroberto    char stub_char, *result;
164181834Sroberto
165181834Sroberto    /* The result cannot be larger than the input PATH. */
166181834Sroberto    result = strdup( path );
167289997Sglebius	if (result == NULL) {
168289997Sglebius	return NULL;    /* couldn't allocate memory    */
169289997Sglebius	}
170181834Sroberto    stub_char = (*path == '/') ? '/' : '.';
171181834Sroberto
172181834Sroberto    /* Walk along RESULT looking for things to compact. */
173181834Sroberto    i = 0;
174181834Sroberto    while (result[i]) {
175181834Sroberto        while (result[i] != '\0' && result[i] != '/')
176181834Sroberto            i++;
177181834Sroberto
178181834Sroberto        start = i++;
179181834Sroberto
180181834Sroberto        /* If we didn't find any  slashes, then there is nothing left to
181181834Sroberto         * do.
182181834Sroberto         */
183181834Sroberto        if (!result[start])
184181834Sroberto            break;
185181834Sroberto
186181834Sroberto        /* Handle multiple `/'s in a row. */
187181834Sroberto        while (result[i] == '/')
188181834Sroberto            i++;
189181834Sroberto
190181834Sroberto#if !defined (apollo)
191181834Sroberto        if ((start + 1) != i)
192181834Sroberto#else
193181834Sroberto        if ((start + 1) != i && (start != 0 || i != 2))
194181834Sroberto#endif /* apollo */
195181834Sroberto        {
196181834Sroberto            strcpy( result + start + 1, result + i );
197181834Sroberto            i = start + 1;
198181834Sroberto        }
199181834Sroberto
200181834Sroberto        /* Handle backquoted `/'. */
201181834Sroberto        if (start > 0 && result[start - 1] == '\\')
202181834Sroberto            continue;
203181834Sroberto
204181834Sroberto        /* Check for trailing `/', and `.' by itself. */
205181834Sroberto        if ((start && !result[i])
206181834Sroberto            || (result[i] == '.' && !result[i+1])) {
207181834Sroberto            result[--i] = '\0';
208181834Sroberto            break;
209181834Sroberto        }
210181834Sroberto
211181834Sroberto        /* Check for `../', `./' or trailing `.' by itself. */
212181834Sroberto        if (result[i] == '.') {
213181834Sroberto            /* Handle `./'. */
214181834Sroberto            if (result[i + 1] == '/') {
215181834Sroberto                strcpy( result + i, result + i + 1 );
216181834Sroberto                i = (start < 0) ? 0 : start;
217181834Sroberto                continue;
218181834Sroberto            }
219181834Sroberto
220181834Sroberto            /* Handle `../' or trailing `..' by itself. */
221181834Sroberto            if (result[i + 1] == '.' &&
222181834Sroberto                (result[i + 2] == '/' || !result[i + 2])) {
223181834Sroberto                while (--start > -1 && result[start] != '/')
224181834Sroberto                    ;
225181834Sroberto                strcpy( result + start + 1, result + i + 2 );
226181834Sroberto                i = (start < 0) ? 0 : start;
227181834Sroberto                continue;
228181834Sroberto            }
229181834Sroberto        }
230181834Sroberto    }
231181834Sroberto
232181834Sroberto    if (!*result) {
233181834Sroberto        *result = stub_char;
234181834Sroberto        result[1] = '\0';
235181834Sroberto    }
236181834Sroberto
237181834Sroberto    return result;
238181834Sroberto}
239181834Sroberto
240181834Sroberto/*
241181834Sroberto * Given a  string containing units of information separated  by colons,
242181834Sroberto * return the next one  pointed to by (P_INDEX), or NULL if there are no
243181834Sroberto * more.  Advance (P_INDEX) to the character after the colon.
244181834Sroberto */
245285612Sdelphijstatic char *
246285612Sdelphijextract_colon_unit(char * pzDir, char const * string, int * p_index)
247181834Sroberto{
248285612Sdelphij    char * pzDest = pzDir;
249181834Sroberto    int    ix     = *p_index;
250181834Sroberto
251181834Sroberto    if (string == NULL)
252181834Sroberto        return NULL;
253181834Sroberto
254181834Sroberto    if ((unsigned)ix >= strlen( string ))
255181834Sroberto        return NULL;
256181834Sroberto
257181834Sroberto    {
258285612Sdelphij        char const * pzSrc = string + ix;
259181834Sroberto
260181834Sroberto        while (*pzSrc == ':')  pzSrc++;
261181834Sroberto
262181834Sroberto        for (;;) {
263181834Sroberto            char ch = (*(pzDest++) = *(pzSrc++));
264181834Sroberto            switch (ch) {
265181834Sroberto            case ':':
266181834Sroberto                pzDest[-1] = NUL;
267285612Sdelphij                /* FALLTHROUGH */
268181834Sroberto            case NUL:
269181834Sroberto                goto copy_done;
270181834Sroberto            }
271181834Sroberto
272285612Sdelphij            if ((unsigned long)(pzDest - pzDir) >= AG_PATH_MAX)
273181834Sroberto                break;
274181834Sroberto        } copy_done:;
275181834Sroberto
276285612Sdelphij        ix = (int)(pzSrc - string);
277181834Sroberto    }
278181834Sroberto
279181834Sroberto    if (*pzDir == NUL)
280181834Sroberto        return NULL;
281181834Sroberto
282181834Sroberto    *p_index = ix;
283181834Sroberto    return pzDir;
284181834Sroberto}
285181834Sroberto#endif /* __windows__ / __CYGWIN__ */
286181834Sroberto#endif /* HAVE_PATHFIND */
287181834Sroberto
288181834Sroberto/*
289181834Sroberto * Local Variables:
290181834Sroberto * mode: C
291181834Sroberto * c-file-style: "stroustrup"
292181834Sroberto * indent-tabs-mode: nil
293181834Sroberto * End:
294181834Sroberto * end of compat/pathfind.c */
295