Sync usage with man page.
[netbsd-mini2440.git] / external / bsd / ntp / dist / sntp / libopts / compat / pathfind.c
blobc93f1cc62d8a3582b2122466e1462838c9f39148
1 /* $NetBSD$ */
3 /* -*- Mode: C -*- */
5 /* pathfind.c --- find a FILE MODE along PATH */
7 /*
8 * Author: Gary V Vaughan <gvaughan@oranda.demon.co.uk>
9 * Time-stamp: "2006-09-23 19:46:16 bkorb"
10 * by: bkorb
12 * Id: 8ce7ddfe2378f0b75c91c0ab348a6ad81634fb01
15 /* Code: */
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 )
25 return NULL;
27 #else
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 );
34 /*=export_func pathfind
36 * what: fild a file in a list of directories
38 * ifndef: HAVE_PATHFIND
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 +
44 * ret_type: char*
45 * ret_desc: the path to the located file
47 * doc:
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.
54 * @strong{NOTE}: this function is compiled into @file{libopts} only if
55 * it is not natively supplied.
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
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. :-)
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 )
93 int p_index = 0;
94 int mode_bits = 0;
95 char* pathName = NULL;
96 char zPath[ AG_PATH_MAX + 1 ];
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;
103 * FOR each non-null entry in the colon-separated path, DO ...
105 for (;;) {
106 DIR* dirP;
107 char* colon_unit = extract_colon_unit( zPath, path, &p_index );
110 * IF no more entries, THEN quit
112 if (colon_unit == NULL)
113 break;
115 dirP = opendir( colon_unit );
118 * IF the directory is inaccessable, THEN next directory
120 if (dirP == NULL)
121 continue;
124 * FOR every entry in the given directory, ...
126 for (;;) {
127 struct dirent *entP = readdir( dirP );
129 if (entP == (struct dirent*)NULL)
130 break;
133 * IF the file name matches the one we are looking for, ...
135 if (strcmp( entP->d_name, fileName ) == 0) {
136 char* pzFullName = make_absolute( fileName, colon_unit);
139 * Make sure we can access it in the way we want
141 if (access( pzFullName, mode_bits ) >= 0) {
143 * We can, so normalize the name and return it below
145 pathName = canonicalize_pathname( pzFullName );
148 free( (void*)pzFullName );
149 break;
153 closedir( dirP );
155 if (pathName != NULL)
156 break;
159 return pathName;
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.
167 static char*
168 make_absolute( char const *string, char const *dot_path )
170 char *result;
171 int result_len;
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';
184 } else {
185 result = malloc( 3 + strlen( string ) );
186 result[0] = '.'; result[1] = '/'; result[2] = '\0';
187 result_len = 2;
190 strcpy( result + result_len, string );
193 return result;
197 * Canonicalize PATH, and return a new path. The new path differs from
198 * PATH in that:
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.
207 static char*
208 canonicalize_pathname( char *path )
210 int i, start;
211 char stub_char, *result;
213 /* The result cannot be larger than the input PATH. */
214 result = strdup( path );
216 stub_char = (*path == '/') ? '/' : '.';
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++;
224 start = i++;
226 /* If we didn't find any slashes, then there is nothing left to
227 * do.
229 if (!result[start])
230 break;
232 /* Handle multiple `/'s in a row. */
233 while (result[i] == '/')
234 i++;
236 #if !defined (apollo)
237 if ((start + 1) != i)
238 #else
239 if ((start + 1) != i && (start != 0 || i != 2))
240 #endif /* apollo */
242 strcpy( result + start + 1, result + i );
243 i = start + 1;
246 /* Handle backquoted `/'. */
247 if (start > 0 && result[start - 1] == '\\')
248 continue;
250 /* Check for trailing `/', and `.' by itself. */
251 if ((start && !result[i])
252 || (result[i] == '.' && !result[i+1])) {
253 result[--i] = '\0';
254 break;
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;
266 /* Handle `../' or trailing `..' by itself. */
267 if (result[i + 1] == '.' &&
268 (result[i + 2] == '/' || !result[i + 2])) {
269 while (--start > -1 && result[start] != '/')
271 strcpy( result + start + 1, result + i + 2 );
272 i = (start < 0) ? 0 : start;
273 continue;
278 if (!*result) {
279 *result = stub_char;
280 result[1] = '\0';
283 return result;
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.
291 static char*
292 extract_colon_unit( char* pzDir, char const *string, int *p_index )
294 char* pzDest = pzDir;
295 int ix = *p_index;
297 if (string == NULL)
298 return NULL;
300 if ((unsigned)ix >= strlen( string ))
301 return NULL;
304 char const* pzSrc = string + ix;
306 while (*pzSrc == ':') pzSrc++;
308 for (;;) {
309 char ch = (*(pzDest++) = *(pzSrc++));
310 switch (ch) {
311 case ':':
312 pzDest[-1] = NUL;
313 case NUL:
314 goto copy_done;
317 if ((pzDest - pzDir) >= AG_PATH_MAX)
318 break;
319 } copy_done:;
321 ix = pzSrc - string;
324 if (*pzDir == NUL)
325 return NULL;
327 *p_index = ix;
328 return pzDir;
330 #endif /* __windows__ / __CYGWIN__ */
331 #endif /* HAVE_PATHFIND */
334 * Local Variables:
335 * mode: C
336 * c-file-style: "stroustrup"
337 * indent-tabs-mode: nil
338 * End:
339 * end of compat/pathfind.c */