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