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