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