1 /* $NetBSD: pathfind.c,v 1.9 2024/08/18 20:47:25 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 } else {
121 if (dot_path && dot_path[0]) {
122 result = malloc( 2 + strlen( dot_path ) + strlen( string ) );
123 strcpy( result, dot_path );
124 result_len = (int)strlen(result);
125 if (result[result_len - 1] != '/') {
126 result[result_len++] = '/';
127 result[result_len] = '\0';
128 }
129 } else {
130 result = malloc( 3 + strlen( string ) );
131 result[0] = '.'; result[1] = '/'; result[2] = '\0';
132 result_len = 2;
133 }
134
135 strcpy( result + result_len, string );
136 }
137
138 return result;
139 }
140
141 /*
142 * Canonicalize PATH, and return a new path. The new path differs from
143 * PATH in that:
144 *
145 * Multiple `/'s are collapsed to a single `/'.
146 * Leading `./'s are removed.
147 * Trailing `/.'s are removed.
148 * Trailing `/'s are removed.
149 * Non-leading `../'s and trailing `..'s are handled by removing
150 * portions of the path.
151 */
152 static char *
canonicalize_pathname(char * path)153 canonicalize_pathname( char *path )
154 {
155 int i, start;
156 char stub_char, *result;
157
158 /* The result cannot be larger than the input PATH. */
159 result = strdup( path );
160
161 stub_char = (*path == '/') ? '/' : '.';
162
163 /* Walk along RESULT looking for things to compact. */
164 i = 0;
165 while (result[i]) {
166 while (result[i] != '\0' && result[i] != '/')
167 i++;
168
169 start = i++;
170
171 /* If we didn't find any slashes, then there is nothing left to
172 * do.
173 */
174 if (!result[start])
175 break;
176
177 /* Handle multiple `/'s in a row. */
178 while (result[i] == '/')
179 i++;
180
181 #if !defined (apollo)
182 if ((start + 1) != i)
183 #else
184 if ((start + 1) != i && (start != 0 || i != 2))
185 #endif /* apollo */
186 {
187 strcpy( result + start + 1, result + i );
188 i = start + 1;
189 }
190
191 /* Handle backquoted `/'. */
192 if (start > 0 && result[start - 1] == '\\')
193 continue;
194
195 /* Check for trailing `/', and `.' by itself. */
196 if ((start && !result[i])
197 || (result[i] == '.' && !result[i+1])) {
198 result[--i] = '\0';
199 break;
200 }
201
202 /* Check for `../', `./' or trailing `.' by itself. */
203 if (result[i] == '.') {
204 /* Handle `./'. */
205 if (result[i + 1] == '/') {
206 strcpy( result + i, result + i + 1 );
207 i = (start < 0) ? 0 : start;
208 continue;
209 }
210
211 /* Handle `../' or trailing `..' by itself. */
212 if (result[i + 1] == '.' &&
213 (result[i + 2] == '/' || !result[i + 2])) {
214 while (--start > -1 && result[start] != '/')
215 ;
216 strcpy( result + start + 1, result + i + 2 );
217 i = (start < 0) ? 0 : start;
218 continue;
219 }
220 }
221 }
222
223 if (!*result) {
224 *result = stub_char;
225 result[1] = '\0';
226 }
227
228 return result;
229 }
230
231 /*
232 * Given a string containing units of information separated by colons,
233 * return the next one pointed to by (P_INDEX), or NULL if there are no
234 * more. Advance (P_INDEX) to the character after the colon.
235 */
236 static char *
extract_colon_unit(char * pzDir,char const * string,int * p_index)237 extract_colon_unit(char * pzDir, char const * string, int * p_index)
238 {
239 char * pzDest = pzDir;
240 int ix = *p_index;
241
242 if (string == NULL)
243 return NULL;
244
245 if ((unsigned)ix >= strlen( string ))
246 return NULL;
247
248 {
249 char const * pzSrc = string + ix;
250
251 while (*pzSrc == ':') pzSrc++;
252
253 for (;;) {
254 char ch = (*(pzDest++) = *(pzSrc++));
255 switch (ch) {
256 case ':':
257 pzDest[-1] = NUL;
258 /* FALLTHROUGH */
259 case NUL:
260 goto copy_done;
261 }
262
263 if ((unsigned long)(pzDest - pzDir) >= AG_PATH_MAX)
264 break;
265 } copy_done:;
266
267 ix = (int)(pzSrc - string);
268 }
269
270 if (*pzDir == NUL)
271 return NULL;
272
273 *p_index = ix;
274 return pzDir;
275 }
276 #endif /* __windows__ / __CYGWIN__ */
277 #endif /* HAVE_PATHFIND */
278
279 /*
280 * Local Variables:
281 * mode: C
282 * c-file-style: "stroustrup"
283 * indent-tabs-mode: nil
284 * End:
285 * end of compat/pathfind.c */
286