xref: /freebsd/contrib/ntp/sntp/libopts/compat/pathfind.c (revision 40a8ac8f62b535d30349faf28cf47106b7041b83)
1 /*  -*- Mode: C -*-  */
2 
3 /* pathfind.c --- find a FILE  MODE along PATH */
4 
5 /*
6  * Author:           Gary V Vaughan <gvaughan@oranda.demon.co.uk>
7  * Time-stamp:       "2006-09-23 19:46:16 bkorb"
8  * Created:          Tue Jun 24 15:07:31 1997
9  * Last Modified:    $Date: 2006/11/27 01:52:23 $
10  *            by: bkorb
11  *
12  * $Id: pathfind.c,v 4.10 2006/11/27 01:52:23 bkorb Exp $
13  */
14 
15 /* Code: */
16 
17 #include "compat.h"
18 #ifndef HAVE_PATHFIND
19 #if defined(__windows__) && !defined(__CYGWIN__)
20 char*
21 pathfind( char const*  path,
22           char const*  fileName,
23           char const*  mode )
24 {
25     return NULL;
26 }
27 #else
28 
29 static char* make_absolute( char const *string, char const *dot_path );
30 static char* canonicalize_pathname( char *path );
31 static char* extract_colon_unit( char* dir, char const *string, int *p_index );
32 
33 
34 /*=export_func pathfind
35  *
36  * what: fild a file in a list of directories
37  *
38  * ifndef: HAVE_PATHFIND
39  *
40  * arg:  + char const* + path + colon separated list of search directories +
41  * arg:  + char const* + file + the name of the file to look for +
42  * arg:  + char const* + mode + the mode bits that must be set to match +
43  *
44  * ret_type:  char*
45  * ret_desc:  the path to the located file
46  *
47  * doc:
48  *
49  * pathfind looks for a a file with name "FILE" and "MODE" access
50  * along colon delimited "PATH", and returns the full pathname as a
51  * string, or NULL if not found.  If "FILE" contains a slash, then
52  * it is treated as a relative or absolute path and "PATH" is ignored.
53  *
54  * @strong{NOTE}: this function is compiled into @file{libopts} only if
55  * it is not natively supplied.
56  *
57  * The "MODE" argument is a string of option letters chosen from the
58  * list below:
59  * @example
60  *          Letter    Meaning
61  *          r         readable
62  *          w         writable
63  *          x         executable
64  *          f         normal file       (NOT IMPLEMENTED)
65  *          b         block special     (NOT IMPLEMENTED)
66  *          c         character special (NOT IMPLEMENTED)
67  *          d         directory         (NOT IMPLEMENTED)
68  *          p         FIFO (pipe)       (NOT IMPLEMENTED)
69  *          u         set user ID bit   (NOT IMPLEMENTED)
70  *          g         set group ID bit  (NOT IMPLEMENTED)
71  *          k         sticky bit        (NOT IMPLEMENTED)
72  *          s         size nonzero      (NOT IMPLEMENTED)
73  * @end example
74  *
75  * example:
76  * To find the "ls" command using the "PATH" environment variable:
77  * @example
78  *    #include <stdlib.h>
79  *    char* pz_ls = pathfind( getenv("PATH"), "ls", "rx" );
80  *    <<do whatever with pz_ls>>
81  *    free( pz_ls );
82  * @end example
83  * The path is allocated with @code{malloc(3C)}, so you must @code{free(3C)}
84  * the result.  Also, do not use unimplemented file modes.  :-)
85  *
86  * err:  returns NULL if the file is not found.
87 =*/
88 char*
89 pathfind( char const*  path,
90           char const*  fileName,
91           char const*  mode )
92 {
93     int   p_index   = 0;
94     int   mode_bits = 0;
95     char* pathName  = NULL;
96     char  zPath[ AG_PATH_MAX + 1 ];
97 
98     if (strchr( mode, 'r' )) mode_bits |= R_OK;
99     if (strchr( mode, 'w' )) mode_bits |= W_OK;
100     if (strchr( mode, 'x' )) mode_bits |= X_OK;
101 
102     /*
103      *  FOR each non-null entry in the colon-separated path, DO ...
104      */
105     for (;;) {
106         DIR*  dirP;
107         char* colon_unit = extract_colon_unit( zPath, path, &p_index );
108 
109         /*
110          *  IF no more entries, THEN quit
111          */
112         if (colon_unit == NULL)
113             break;
114 
115         dirP = opendir( colon_unit );
116 
117         /*
118          *  IF the directory is inaccessable, THEN next directory
119          */
120         if (dirP == NULL)
121             continue;
122 
123         /*
124          *  FOR every entry in the given directory, ...
125          */
126         for (;;) {
127             struct dirent *entP = readdir( dirP );
128 
129             if (entP == (struct dirent*)NULL)
130                 break;
131 
132             /*
133              *  IF the file name matches the one we are looking for, ...
134              */
135             if (strcmp( entP->d_name, fileName ) == 0) {
136                 char* pzFullName = make_absolute( fileName, colon_unit);
137 
138                 /*
139                  *  Make sure we can access it in the way we want
140                  */
141                 if (access( pzFullName, mode_bits ) >= 0) {
142                     /*
143                      *  We can, so normalize the name and return it below
144                      */
145                     pathName = canonicalize_pathname( pzFullName );
146                 }
147 
148                 free( (void*)pzFullName );
149                 break;
150             }
151         }
152 
153         closedir( dirP );
154 
155         if (pathName != NULL)
156             break;
157     }
158 
159     return pathName;
160 }
161 
162 /*
163  * Turn STRING  (a pathname) into an  absolute  pathname, assuming  that
164  * DOT_PATH contains the symbolic location of  `.'.  This always returns
165  * a new string, even if STRING was an absolute pathname to begin with.
166  */
167 static char*
168 make_absolute( char const *string, char const *dot_path )
169 {
170     char *result;
171     int result_len;
172 
173     if (!dot_path || *string == '/') {
174         result = strdup( string );
175     } else {
176         if (dot_path && dot_path[0]) {
177             result = malloc( 2 + strlen( dot_path ) + strlen( string ) );
178             strcpy( result, dot_path );
179             result_len = strlen( result );
180             if (result[result_len - 1] != '/') {
181                 result[result_len++] = '/';
182                 result[result_len] = '\0';
183             }
184         } else {
185             result = malloc( 3 + strlen( string ) );
186             result[0] = '.'; result[1] = '/'; result[2] = '\0';
187             result_len = 2;
188         }
189 
190         strcpy( result + result_len, string );
191     }
192 
193     return result;
194 }
195 
196 /*
197  * Canonicalize PATH, and return a  new path.  The new path differs from
198  * PATH in that:
199  *
200  *    Multiple `/'s     are collapsed to a single `/'.
201  *    Leading `./'s     are removed.
202  *    Trailing `/.'s    are removed.
203  *    Trailing `/'s     are removed.
204  *    Non-leading `../'s and trailing `..'s are handled by removing
205  *                    portions of the path.
206  */
207 static char*
208 canonicalize_pathname( char *path )
209 {
210     int i, start;
211     char stub_char, *result;
212 
213     /* The result cannot be larger than the input PATH. */
214     result = strdup( path );
215 
216     stub_char = (*path == '/') ? '/' : '.';
217 
218     /* Walk along RESULT looking for things to compact. */
219     i = 0;
220     while (result[i]) {
221         while (result[i] != '\0' && result[i] != '/')
222             i++;
223 
224         start = i++;
225 
226         /* If we didn't find any  slashes, then there is nothing left to
227          * do.
228          */
229         if (!result[start])
230             break;
231 
232         /* Handle multiple `/'s in a row. */
233         while (result[i] == '/')
234             i++;
235 
236 #if !defined (apollo)
237         if ((start + 1) != i)
238 #else
239         if ((start + 1) != i && (start != 0 || i != 2))
240 #endif /* apollo */
241         {
242             strcpy( result + start + 1, result + i );
243             i = start + 1;
244         }
245 
246         /* Handle backquoted `/'. */
247         if (start > 0 && result[start - 1] == '\\')
248             continue;
249 
250         /* Check for trailing `/', and `.' by itself. */
251         if ((start && !result[i])
252             || (result[i] == '.' && !result[i+1])) {
253             result[--i] = '\0';
254             break;
255         }
256 
257         /* Check for `../', `./' or trailing `.' by itself. */
258         if (result[i] == '.') {
259             /* Handle `./'. */
260             if (result[i + 1] == '/') {
261                 strcpy( result + i, result + i + 1 );
262                 i = (start < 0) ? 0 : start;
263                 continue;
264             }
265 
266             /* Handle `../' or trailing `..' by itself. */
267             if (result[i + 1] == '.' &&
268                 (result[i + 2] == '/' || !result[i + 2])) {
269                 while (--start > -1 && result[start] != '/')
270                     ;
271                 strcpy( result + start + 1, result + i + 2 );
272                 i = (start < 0) ? 0 : start;
273                 continue;
274             }
275         }
276     }
277 
278     if (!*result) {
279         *result = stub_char;
280         result[1] = '\0';
281     }
282 
283     return result;
284 }
285 
286 /*
287  * Given a  string containing units of information separated  by colons,
288  * return the next one  pointed to by (P_INDEX), or NULL if there are no
289  * more.  Advance (P_INDEX) to the character after the colon.
290  */
291 static char*
292 extract_colon_unit( char* pzDir, char const *string, int *p_index )
293 {
294     char*  pzDest = pzDir;
295     int    ix     = *p_index;
296 
297     if (string == NULL)
298         return NULL;
299 
300     if ((unsigned)ix >= strlen( string ))
301         return NULL;
302 
303     {
304         char const* pzSrc = string + ix;
305 
306         while (*pzSrc == ':')  pzSrc++;
307 
308         for (;;) {
309             char ch = (*(pzDest++) = *(pzSrc++));
310             switch (ch) {
311             case ':':
312                 pzDest[-1] = NUL;
313             case NUL:
314                 goto copy_done;
315             }
316 
317             if ((pzDest - pzDir) >= AG_PATH_MAX)
318                 break;
319         } copy_done:;
320 
321         ix = pzSrc - string;
322     }
323 
324     if (*pzDir == NUL)
325         return NULL;
326 
327     *p_index = ix;
328     return pzDir;
329 }
330 #endif /* __windows__ / __CYGWIN__ */
331 #endif /* HAVE_PATHFIND */
332 
333 /*
334  * Local Variables:
335  * mode: C
336  * c-file-style: "stroustrup"
337  * indent-tabs-mode: nil
338  * End:
339  * end of compat/pathfind.c */
340