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