xref: /freebsd/contrib/ntp/sntp/libopts/compat/pathfind.c (revision cfd6422a5217410fbd66f7a7a8a64d9d85e61229)
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 *
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 *
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 *
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 *
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 *
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