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