1 /* @(#)searchinpath.c 1.7 19/07/31 Copyright 1999-2019 J. Schilling */
2 #define USE_LARGEFILES
3 #include <schily/mconfig.h>
4 #ifndef lint
5 static UConst char sccsid[] =
6 "@(#)searchinpath.c 1.7 19/07/31 Copyright 1999-2019 J. Schilling";
7 #endif
8 /*
9 * Search a file name in the PATH of the current exeecutable.
10 * Return the path to the file name in allocated space.
11 *
12 * Copyright (c) 1999-2019 J. Schilling
13 */
14 /*
15 * The contents of this file are subject to the terms of the
16 * Common Development and Distribution License, Version 1.0 only
17 * (the "License"). You may not use this file except in compliance
18 * with the License.
19 *
20 * See the file CDDL.Schily.txt in this distribution for details.
21 * A copy of the CDDL is also available via the Internet at
22 * http://www.opensource.org/licenses/cddl1.txt
23 *
24 * When distributing Covered Code, include this CDDL HEADER in each
25 * file and include the License file CDDL.Schily.txt from this distribution.
26 */
27
28 /*
29 * Since we need to call stat() and since this is not a predictable call,
30 * we always compile this module in largefile mode.
31 * See #define USE_LARGEFILES before #include <schily/mconfig.h>
32 */
33
34 #include <schily/mconfig.h>
35 #include <schily/string.h>
36 #include <schily/unistd.h>
37 #include <schily/stdlib.h> /* getexecname() */
38 #include <schily/stat.h>
39 #include <schily/standard.h>
40 #include <schily/schily.h>
41 #include <schily/errno.h>
42
43
44 #define NAMEMAX 4096
45
46 EXPORT char *searchfileinpath __PR((char *name, int mode,
47 int file_mode,
48 char *path));
49 LOCAL char *searchonefile __PR((char *name, int mode,
50 BOOL plain_file,
51 char *xn,
52 char *nbuf,
53 char *np, char *ep));
54 #if defined(__DJGPP__)
55 LOCAL char *strbs2s __PR((char *s));
56 #endif
57
58 #ifdef JOS
59 #define enofile(t) ((t) == EMISSDIR || \
60 (t) == ENOFILE || \
61 (t) == EISADIR || \
62 (t) == EIOERR)
63 #else
64 #define enofile(t) ((t) == ENOENT || \
65 (t) == ENOTDIR || \
66 (t) == EISDIR || \
67 (t) == EIO)
68 #endif
69
70 /*
71 * Search for the "name" file in the PATH of the user.
72 * Assume that the file is ... bin/../name.
73 */
74 EXPORT char *
searchfileinpath(name,mode,file_mode,path)75 searchfileinpath(name, mode, file_mode, path)
76 char *name; /* Find <execname>/../name in PATH */
77 int mode; /* Mode for access() e.g. X_OK */
78 int file_mode; /* How to check files */
79 char *path; /* PATH to use if not NULL */
80 {
81 char pbuf[NAMEMAX];
82 char *nbuf = pbuf;
83 char *np;
84 char *ep;
85 char *xn;
86 int nlen = strlen(name);
87 int oerrno = geterrno();
88 int err = 0;
89 #ifdef HAVE_GETEXECNAME
90 char *pn = (char *)getexecname(); /* pn is on the stack */
91 #else
92 char *pn = getexecpath(); /* pn is from strdup() */
93 char ebuf[NAMEMAX];
94
95 if (pn) {
96 strlcpy(ebuf, pn, sizeof (ebuf));
97 free(pn);
98 pn = ebuf;
99 }
100 #endif
101
102 if (pn == NULL)
103 xn = get_progname();
104 else
105 xn = pn;
106 if ((np = strrchr(xn, '/')) != NULL)
107 xn = ++np;
108
109 /*
110 * getexecname() is the best choice for our seach. getexecname()
111 * returns either "foo" (if called from the current directory) or
112 * an absolute path after symlinks have been resolved.
113 * If getexecname() returns a path with slashes, try to search
114 * first relatively to the known location of the current binary.
115 */
116 if ((file_mode & SIP_ONLY_PATH) == 0 &&
117 pn != NULL && strchr(pn, '/') != NULL) {
118 strlcpy(nbuf, pn, sizeof (pbuf));
119 np = nbuf + strlen(nbuf);
120
121 while (np > nbuf && np[-1] != '/')
122 *--np = '\0';
123 pn = &nbuf[sizeof (pbuf) - 1];
124 if ((np = searchonefile(name, mode,
125 file_mode & (SIP_PLAIN_FILE|SIP_NO_STRIPBIN),
126 xn,
127 nbuf, np, pn)) != NULL) {
128 seterrno(oerrno);
129 return (np);
130 }
131 }
132
133 if (file_mode & SIP_NO_PATH)
134 return (NULL);
135
136 if (path == NULL)
137 path = getenv("PATH");
138 if (path == NULL)
139 return (NULL);
140
141
142 #ifdef __DJGPP__
143 path = strdup(path);
144 if (path == NULL)
145 return (NULL);
146 strbs2s(path); /* PATH under DJGPP can contain both slashes */
147 pn = path; /* Remember PATH to be able to free() it */
148 #endif
149
150 /*
151 * A PATH name search should lead us to the path under which we
152 * called the binary, but not necessarily to the install path as
153 * we may have been called thorugh a symlink or hardlink. In case
154 * of a symlink, we can follow the link. In case of a hardlink, we
155 * are lost.
156 */
157 ep = &nbuf[sizeof (pbuf) - 1];
158 for (;;) {
159 np = nbuf;
160 while (*path != PATH_ENV_DELIM && *path != '\0' &&
161 np < &nbuf[sizeof (pbuf) - nlen])
162 *np++ = *path++;
163 *np = '\0';
164 if ((np = searchonefile(name, mode,
165 file_mode & (SIP_PLAIN_FILE|SIP_NO_STRIPBIN),
166 xn,
167 nbuf, np, ep)) != NULL) {
168 #ifdef __DJGPP__
169 free(pn);
170 #endif
171 seterrno(oerrno);
172 return (np);
173 }
174 if (err == 0) {
175 err = geterrno();
176 if (enofile(err))
177 err = 0;
178 }
179 if (*path == '\0')
180 break;
181 path++;
182 }
183 #ifdef __DJGPP__
184 free(pn);
185 #endif
186 if (err)
187 seterrno(err);
188 else
189 seterrno(oerrno);
190 return (NULL);
191 }
192
193 LOCAL char *
searchonefile(name,mode,plain_file,xn,nbuf,np,ep)194 searchonefile(name, mode, plain_file, xn, nbuf, np, ep)
195 register char *name; /* Find <execname>/../name in PATH */
196 int mode; /* Mode for access() e.g. X_OK */
197 BOOL plain_file; /* Whether to check only plain files */
198 char *xn; /* The basename of the executable */
199 register char *nbuf; /* Name buffer base */
200 register char *np; /* Where to append name to path */
201 register char *ep; /* Point to last valid char in nbuf */
202 {
203 struct stat sb;
204
205 while (np > nbuf && np[-1] == '/')
206 *--np = '\0';
207 if (xn) {
208 *np++ = '/';
209 strlcpy(np, xn, ep - np);
210 if (stat(nbuf, &sb) < 0)
211 return (NULL);
212 if (!S_ISREG(sb.st_mode))
213 return (NULL);
214 *--np = '\0';
215 }
216 if ((plain_file & SIP_NO_STRIPBIN) == 0) {
217 if (np >= &nbuf[4] && streql(&np[-4], "/bin"))
218 np = &np[-4];
219 }
220 plain_file &= SIP_PLAIN_FILE;
221 *np++ = '/';
222 *np = '\0';
223 strlcpy(np, name, ep - np);
224
225 seterrno(0);
226 if (stat(nbuf, &sb) >= 0) {
227 if ((!plain_file || S_ISREG(sb.st_mode)) &&
228 (eaccess(nbuf, mode) >= 0)) {
229 return (strdup(nbuf));
230 }
231 if (geterrno() == 0)
232 seterrno(EACCES);
233 }
234 return (NULL);
235 }
236
237 #ifdef __DJGPP__
238 LOCAL char *
strbs2s(s)239 strbs2s(s)
240 char *s;
241 {
242 char *tmp = s;
243
244 if (tmp) {
245 while (*tmp) {
246 if (*tmp == '\\')
247 *tmp = '/';
248 tmp++;
249 }
250 }
251 return (s);
252 }
253 #endif
254