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 
11 static char *
12 pathfind( 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__)
19 static char *
pathfind(char const * path,char const * fname,char const * mode)20 pathfind( char const * path,
21           char const * fname,
22           char const * mode )
23 {
24     return strdup(fname);
25 }
26 #else
27 
28 static char * make_absolute(char const * string, char const * dot_path);
29 static char * canonicalize_pathname(char * path);
30 static 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  */
39 static char *
pathfind(char const * path,char const * fname,char const * mode)40 pathfind( 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  */
112 static char *
make_absolute(char const * string,char const * dot_path)113 make_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  */
161 static char *
canonicalize_pathname(char * path)162 canonicalize_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  */
247 static char *
extract_colon_unit(char * pzDir,char const * string,int * p_index)248 extract_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