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