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