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