xref: /openbsd-src/bin/ksh/path.c (revision a28daedfc357b214be5c701aa8ba8adb29a7f1c2)
1 /*	$OpenBSD: path.c,v 1.12 2005/03/30 17:16:37 deraadt Exp $	*/
2 
3 #include "sh.h"
4 #include <sys/stat.h>
5 
6 /*
7  *	Contains a routine to search a : separated list of
8  *	paths (a la CDPATH) and make appropriate file names.
9  *	Also contains a routine to simplify .'s and ..'s out of
10  *	a path name.
11  *
12  *	Larry Bouzane (larry@cs.mun.ca)
13  */
14 
15 static char	*do_phys_path(XString *, char *, const char *);
16 
17 /*
18  *	Makes a filename into result using the following algorithm.
19  *	- make result NULL
20  *	- if file starts with '/', append file to result & set cdpathp to NULL
21  *	- if file starts with ./ or ../ append cwd and file to result
22  *	  and set cdpathp to NULL
23  *	- if the first element of cdpathp doesnt start with a '/' xx or '.' xx
24  *	  then cwd is appended to result.
25  *	- the first element of cdpathp is appended to result
26  *	- file is appended to result
27  *	- cdpathp is set to the start of the next element in cdpathp (or NULL
28  *	  if there are no more elements.
29  *	The return value indicates whether a non-null element from cdpathp
30  *	was appended to result.
31  */
32 int
33 make_path(const char *cwd, const char *file,
34     char **cdpathp,		/* & of : separated list */
35     XString *xsp,
36     int *phys_pathp)
37 {
38 	int	rval = 0;
39 	int	use_cdpath = 1;
40 	char	*plist;
41 	int	len;
42 	int	plen = 0;
43 	char	*xp = Xstring(*xsp, xp);
44 
45 	if (!file)
46 		file = null;
47 
48 	if (file[0] == '/') {
49 		*phys_pathp = 0;
50 		use_cdpath = 0;
51 	} else {
52 		if (file[0] == '.') {
53 			char c = file[1];
54 
55 			if (c == '.')
56 				c = file[2];
57 			if (c == '/' || c == '\0')
58 				use_cdpath = 0;
59 		}
60 
61 		plist = *cdpathp;
62 		if (!plist)
63 			use_cdpath = 0;
64 		else if (use_cdpath) {
65 			char *pend;
66 
67 			for (pend = plist; *pend && *pend != ':'; pend++)
68 				;
69 			plen = pend - plist;
70 			*cdpathp = *pend ? ++pend : (char *) 0;
71 		}
72 
73 		if ((use_cdpath == 0 || !plen || plist[0] != '/') &&
74 		    (cwd && *cwd)) {
75 			len = strlen(cwd);
76 			XcheckN(*xsp, xp, len);
77 			memcpy(xp, cwd, len);
78 			xp += len;
79 			if (cwd[len - 1] != '/')
80 				Xput(*xsp, xp, '/');
81 		}
82 		*phys_pathp = Xlength(*xsp, xp);
83 		if (use_cdpath && plen) {
84 			XcheckN(*xsp, xp, plen);
85 			memcpy(xp, plist, plen);
86 			xp += plen;
87 			if (plist[plen - 1] != '/')
88 				Xput(*xsp, xp, '/');
89 			rval = 1;
90 		}
91 	}
92 
93 	len = strlen(file) + 1;
94 	XcheckN(*xsp, xp, len);
95 	memcpy(xp, file, len);
96 
97 	if (!use_cdpath)
98 		*cdpathp = (char *) 0;
99 
100 	return rval;
101 }
102 
103 /*
104  * Simplify pathnames containing "." and ".." entries.
105  * ie, simplify_path("/a/b/c/./../d/..") returns "/a/b"
106  */
107 void
108 simplify_path(char *path)
109 {
110 	char	*cur;
111 	char	*t;
112 	int	isrooted;
113 	char	*very_start = path;
114 	char	*start;
115 
116 	if (!*path)
117 		return;
118 
119 	if ((isrooted = path[0] == '/'))
120 		very_start++;
121 
122 	/* Before			After
123 	 *  /foo/			/foo
124 	 *  /foo/../../bar		/bar
125 	 *  /foo/./blah/..		/foo
126 	 *  .				.
127 	 *  ..				..
128 	 *  ./foo			foo
129 	 *  foo/../../../bar		../../bar
130 	 */
131 
132 	for (cur = t = start = very_start; ; ) {
133 		/* treat multiple '/'s as one '/' */
134 		while (*t == '/')
135 			t++;
136 
137 		if (*t == '\0') {
138 			if (cur == path)
139 				/* convert empty path to dot */
140 				*cur++ = '.';
141 			*cur = '\0';
142 			break;
143 		}
144 
145 		if (t[0] == '.') {
146 			if (!t[1] || t[1] == '/') {
147 				t += 1;
148 				continue;
149 			} else if (t[1] == '.' && (!t[2] || t[2] == '/')) {
150 				if (!isrooted && cur == start) {
151 					if (cur != very_start)
152 						*cur++ = '/';
153 					*cur++ = '.';
154 					*cur++ = '.';
155 					start = cur;
156 				} else if (cur != start)
157 					while (--cur > start && *cur != '/')
158 						;
159 				t += 2;
160 				continue;
161 			}
162 		}
163 
164 		if (cur != very_start)
165 			*cur++ = '/';
166 
167 		/* find/copy next component of pathname */
168 		while (*t && *t != '/')
169 			*cur++ = *t++;
170 	}
171 }
172 
173 
174 void
175 set_current_wd(char *path)
176 {
177 	int len;
178 	char *p = path;
179 
180 	if (!p && !(p = ksh_get_wd((char *) 0, 0)))
181 		p = null;
182 
183 	len = strlen(p) + 1;
184 
185 	if (len > current_wd_size)
186 		current_wd = aresize(current_wd, current_wd_size = len, APERM);
187 	memcpy(current_wd, p, len);
188 	if (p != path && p != null)
189 		afree(p, ATEMP);
190 }
191 
192 char *
193 get_phys_path(const char *path)
194 {
195 	XString xs;
196 	char *xp;
197 
198 	Xinit(xs, xp, strlen(path) + 1, ATEMP);
199 
200 	xp = do_phys_path(&xs, xp, path);
201 
202 	if (!xp)
203 		return (char *) 0;
204 
205 	if (Xlength(xs, xp) == 0)
206 		Xput(xs, xp, '/');
207 	Xput(xs, xp, '\0');
208 
209 	return Xclose(xs, xp);
210 }
211 
212 static char *
213 do_phys_path(XString *xsp, char *xp, const char *path)
214 {
215 	const char *p, *q;
216 	int len, llen;
217 	int savepos;
218 	char lbuf[PATH];
219 
220 	Xcheck(*xsp, xp);
221 	for (p = path; p; p = q) {
222 		while (*p == '/')
223 			p++;
224 		if (!*p)
225 			break;
226 		len = (q = strchr(p, '/')) ? q - p : strlen(p);
227 		if (len == 1 && p[0] == '.')
228 			continue;
229 		if (len == 2 && p[0] == '.' && p[1] == '.') {
230 			while (xp > Xstring(*xsp, xp)) {
231 				xp--;
232 				if (*xp == '/')
233 					break;
234 			}
235 			continue;
236 		}
237 
238 		savepos = Xsavepos(*xsp, xp);
239 		Xput(*xsp, xp, '/');
240 		XcheckN(*xsp, xp, len + 1);
241 		memcpy(xp, p, len);
242 		xp += len;
243 		*xp = '\0';
244 
245 		llen = readlink(Xstring(*xsp, xp), lbuf, sizeof(lbuf) - 1);
246 		if (llen < 0) {
247 			/* EINVAL means it wasn't a symlink... */
248 			if (errno != EINVAL)
249 				return (char *) 0;
250 			continue;
251 		}
252 		lbuf[llen] = '\0';
253 
254 		/* If absolute path, start from scratch.. */
255 		xp = lbuf[0] == '/' ? Xstring(*xsp, xp) :
256 		    Xrestpos(*xsp, xp, savepos);
257 		if (!(xp = do_phys_path(xsp, xp, lbuf)))
258 			return (char *) 0;
259 	}
260 	return xp;
261 }
262 
263 #ifdef	TEST
264 
265 int
266 main(void)
267 {
268 	int	rv;
269 	char	*cp, cdpath[256], pwd[256], file[256], result[256];
270 
271 	printf("enter CDPATH: "); gets(cdpath);
272 	printf("enter PWD: "); gets(pwd);
273 	while (1) {
274 		if (printf("Enter file: "), gets(file) == 0)
275 			return 0;
276 		cp = cdpath;
277 		do {
278 			rv = make_path(pwd, file, &cp, result, sizeof(result));
279 			printf("make_path returns (%d), \"%s\" ", rv, result);
280 			simplify_path(result);
281 			printf("(simpifies to \"%s\")\n", result);
282 		} while (cp);
283 	}
284 }
285 #endif	/* TEST */
286