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