xref: /openbsd-src/sys/kern/vfs_getcwd.c (revision 46035553bfdd96e63c94e32da0210227ec2e3cf1)
1 /* $OpenBSD: vfs_getcwd.c,v 1.36 2019/05/30 13:34:54 beck Exp $ */
2 /* $NetBSD: vfs_getcwd.c,v 1.3.2.3 1999/07/11 10:24:09 sommerfeld Exp $ */
3 
4 /*
5  * Copyright (c) 1999 The NetBSD Foundation, Inc.
6  * All rights reserved.
7  *
8  * This code is derived from software contributed to The NetBSD Foundation
9  * by Bill Sommerfeld.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30  * POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 #include <sys/param.h>
34 #include <sys/systm.h>
35 #include <sys/namei.h>
36 #include <sys/filedesc.h>
37 #include <sys/kernel.h>
38 #include <sys/stat.h>
39 #include <sys/lock.h>
40 #include <sys/vnode.h>
41 #include <sys/mount.h>
42 #include <sys/ktrace.h>
43 #include <sys/proc.h>
44 #include <sys/uio.h>
45 #include <sys/malloc.h>
46 #include <sys/dirent.h>
47 #include <ufs/ufs/dir.h>	/* only for DIRBLKSIZ */
48 
49 #include <sys/syscallargs.h>
50 
51 
52 /* Find parent vnode of *lvpp, return in *uvpp */
53 int
54 vfs_getcwd_scandir(struct vnode **lvpp, struct vnode **uvpp, char **bpp,
55     char *bufp, struct proc *p)
56 {
57 	int eofflag, tries, dirbuflen = 0, len, reclen, error = 0;
58 	off_t off;
59 	struct uio uio;
60 	struct iovec iov;
61 	char *dirbuf = NULL;
62 	ino_t fileno;
63 	struct vattr va;
64 	struct vnode *uvp = NULL;
65 	struct vnode *lvp = *lvpp;
66 	struct componentname cn;
67 
68 	tries = 0;
69 
70 	/*
71 	 * If we want the filename, get some info we need while the
72 	 * current directory is still locked.
73 	 */
74 	if (bufp != NULL) {
75 		error = VOP_GETATTR(lvp, &va, p->p_ucred, p);
76 		if (error) {
77 			vput(lvp);
78 			*lvpp = NULL;
79 			*uvpp = NULL;
80 			return (error);
81 		}
82 	}
83 
84 	cn.cn_nameiop = LOOKUP;
85 	cn.cn_flags = ISLASTCN | ISDOTDOT | RDONLY;
86 	cn.cn_proc = p;
87 	cn.cn_cred = p->p_ucred;
88 	cn.cn_pnbuf = NULL;
89 	cn.cn_nameptr = "..";
90 	cn.cn_namelen = 2;
91 	cn.cn_consume = 0;
92 
93 	/* Get parent vnode using lookup of '..' */
94 	error = VOP_LOOKUP(lvp, uvpp, &cn);
95 	if (error) {
96 		vput(lvp);
97 		*lvpp = NULL;
98 		*uvpp = NULL;
99 		return (error);
100 	}
101 
102 	uvp = *uvpp;
103 
104 	/* If we don't care about the pathname, we're done */
105 	if (bufp == NULL) {
106 		error = 0;
107 		goto out;
108 	}
109 
110 	fileno = va.va_fileid;
111 
112 	dirbuflen = DIRBLKSIZ;
113 	if (dirbuflen < va.va_blocksize)
114 		dirbuflen = va.va_blocksize;
115 	/* XXX we need some limit for fuse, 1 MB should be enough */
116 	if (dirbuflen > 0xfffff) {
117 		error = EINVAL;
118 		goto out;
119 	}
120 	dirbuf = malloc(dirbuflen, M_TEMP, M_WAITOK);
121 
122 	off = 0;
123 
124 	do {
125 		char   *cpos;
126 		struct dirent *dp;
127 
128 		iov.iov_base = dirbuf;
129 		iov.iov_len = dirbuflen;
130 
131 		uio.uio_iov = &iov;
132 		uio.uio_iovcnt = 1;
133 		uio.uio_offset = off;
134 		uio.uio_resid = dirbuflen;
135 		uio.uio_segflg = UIO_SYSSPACE;
136 		uio.uio_rw = UIO_READ;
137 		uio.uio_procp = p;
138 
139 		eofflag = 0;
140 
141 		/* Call VOP_READDIR of parent */
142 		error = VOP_READDIR(uvp, &uio, p->p_ucred, &eofflag);
143 
144 		off = uio.uio_offset;
145 
146 		/* Try again if NFS tosses its cookies */
147 		if (error == EINVAL && tries < 3) {
148 			tries++;
149 			off = 0;
150 			continue;
151 		} else if (error) {
152 			goto out; /* Old userland getcwd() behaviour */
153 		}
154 
155 		cpos = dirbuf;
156 		tries = 0;
157 
158 		/* Scan directory page looking for matching vnode */
159 		for (len = (dirbuflen - uio.uio_resid); len > 0;
160 		     len -= reclen) {
161 			dp = (struct dirent *)cpos;
162 			reclen = dp->d_reclen;
163 
164 			/* Check for malformed directory */
165 			if (reclen < DIRENT_RECSIZE(1) || reclen > len) {
166 				error = EINVAL;
167 				goto out;
168 			}
169 
170 			if (dp->d_fileno == fileno) {
171 				char *bp = *bpp;
172 
173 				if (offsetof(struct dirent, d_name) +
174 				    dp->d_namlen > reclen) {
175 					error = EINVAL;
176 					goto out;
177 				}
178 				bp -= dp->d_namlen;
179 				if (bp <= bufp) {
180 					error = ERANGE;
181 					goto out;
182 				}
183 
184 				memmove(bp, dp->d_name, dp->d_namlen);
185 				error = 0;
186 				*bpp = bp;
187 
188 				goto out;
189 			}
190 
191 			cpos += reclen;
192 		}
193 
194 	} while (!eofflag);
195 
196 	error = ENOENT;
197 
198 out:
199 
200 	vrele(lvp);
201 	*lvpp = NULL;
202 
203 	free(dirbuf, M_TEMP, dirbuflen);
204 
205 	return (error);
206 }
207 
208 /* Do a lookup in the vnode-to-name reverse */
209 int
210 vfs_getcwd_getcache(struct vnode **lvpp, struct vnode **uvpp, char **bpp,
211     char *bufp)
212 {
213 	struct vnode *lvp, *uvp = NULL;
214 	char *obp;
215 	int error, vpid;
216 
217 	lvp = *lvpp;
218 	obp = *bpp;	/* Save original position to restore to on error */
219 
220 	error = cache_revlookup(lvp, uvpp, bpp, bufp);
221 	if (error) {
222 		if (error != -1) {
223 			vput(lvp);
224 			*lvpp = NULL;
225 			*uvpp = NULL;
226 		}
227 
228 		return (error);
229 	}
230 
231 	uvp = *uvpp;
232 	vpid = uvp->v_id;
233 
234 
235 	/* Release current lock before acquiring the parent lock */
236 	VOP_UNLOCK(lvp);
237 
238 	error = vget(uvp, LK_EXCLUSIVE | LK_RETRY);
239 	if (error)
240 		*uvpp = NULL;
241 
242 	/*
243 	 * Verify that vget() succeeded, and check that vnode capability
244 	 * didn't change while we were waiting for the lock.
245 	 */
246 	if (error || (vpid != uvp->v_id)) {
247 		/*
248 		 * Try to get our lock back. If that works, tell the caller to
249 		 * try things the hard way, otherwise give up.
250 		 */
251 		if (!error)
252 			vput(uvp);
253 
254 		*uvpp = NULL;
255 
256 		error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY);
257 		if (!error) {
258 			*bpp = obp; /* restore the buffer */
259 			return (-1);
260 		}
261 	}
262 
263 	vrele(lvp);
264 	*lvpp = NULL;
265 
266 	return (error);
267 }
268 
269 /* Common routine shared by sys___getcwd() and vn_isunder() and sys___realpath() */
270 int
271 vfs_getcwd_common(struct vnode *lvp, struct vnode *rvp, char **bpp, char *bufp,
272     int limit, int flags, struct proc *p)
273 {
274 	struct filedesc *fdp = p->p_fd;
275 	struct vnode *uvp = NULL;
276 	char *bp = NULL;
277 	int error, perms = VEXEC;
278 
279 	if (rvp == NULL) {
280 		rvp = fdp->fd_rdir;
281 		if (rvp == NULL)
282 			rvp = rootvnode;
283 	}
284 
285 	vref(rvp);
286 	vref(lvp);
287 
288 	error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY);
289 	if (error) {
290 		vrele(lvp);
291 		lvp = NULL;
292 		goto out;
293 	}
294 
295 	if (bufp)
296 		bp = *bpp;
297 
298 	if (lvp == rvp) {
299 		if (bp)
300 			*(--bp) = '/';
301 		goto out;
302 	}
303 
304 	/*
305 	 * This loop will terminate when we hit the root, VOP_READDIR() or
306 	 * VOP_LOOKUP() fails, or we run out of space in the user buffer.
307 	 */
308 	do {
309 		if (lvp->v_type != VDIR) {
310 			error = ENOTDIR;
311 			goto out;
312 		}
313 
314 		/* Check for access if caller cares */
315 		if (flags & GETCWD_CHECK_ACCESS) {
316 			error = VOP_ACCESS(lvp, perms, p->p_ucred, p);
317 			if (error)
318 				goto out;
319 			perms = VEXEC|VREAD;
320 		}
321 
322 		/* Step up if we're a covered vnode */
323 		while (lvp->v_flag & VROOT) {
324 			struct vnode *tvp;
325 
326 			if (lvp == rvp)
327 				goto out;
328 
329 			tvp = lvp;
330 			lvp = lvp->v_mount->mnt_vnodecovered;
331 
332 			vput(tvp);
333 
334 			if (lvp == NULL) {
335 				error = ENOENT;
336 				goto out;
337 			}
338 
339 			vref(lvp);
340 
341 			error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY);
342 			if (error) {
343 				vrele(lvp);
344 				lvp = NULL;
345 				goto out;
346 			}
347 		}
348 
349 		/* Look in the name cache */
350 		error = vfs_getcwd_getcache(&lvp, &uvp, &bp, bufp);
351 
352 		if (error == -1) {
353 			/* If that fails, look in the directory */
354 			error = vfs_getcwd_scandir(&lvp, &uvp, &bp, bufp, p);
355 		}
356 
357 		if (error)
358 			goto out;
359 
360 #ifdef DIAGNOSTIC
361 		if (lvp != NULL)
362 			panic("getcwd: oops, forgot to null lvp");
363 		if (bufp && (bp <= bufp)) {
364 			panic("getcwd: oops, went back too far");
365 		}
366 #endif
367 
368 		if (bp)
369 			*(--bp) = '/';
370 
371 		lvp = uvp;
372 		uvp = NULL;
373 		limit--;
374 
375 	} while ((lvp != rvp) && (limit > 0));
376 
377 out:
378 
379 	if (bpp)
380 		*bpp = bp;
381 
382 	if (uvp)
383 		vput(uvp);
384 
385 	if (lvp)
386 		vput(lvp);
387 
388 	vrele(rvp);
389 
390 	return (error);
391 }
392 
393 /* Find pathname of a process's current directory */
394 int
395 sys___getcwd(struct proc *p, void *v, register_t *retval)
396 {
397 	struct sys___getcwd_args *uap = v;
398 	int error, len = SCARG(uap, len);
399 	char *path, *bp;
400 
401 	if (len > MAXPATHLEN * 4)
402 		len = MAXPATHLEN * 4;
403 	else if (len < 2)
404 		return (ERANGE);
405 
406 	path = malloc(len, M_TEMP, M_WAITOK);
407 
408 	bp = &path[len - 1];
409 	*bp = '\0';
410 
411 	/*
412 	 * 5th argument here is "max number of vnodes to traverse".
413 	 * Since each entry takes up at least 2 bytes in the output
414 	 * buffer, limit it to N/2 vnodes for an N byte buffer.
415 	 */
416 	error = vfs_getcwd_common(p->p_fd->fd_cdir, NULL, &bp, path, len/2,
417 	    GETCWD_CHECK_ACCESS, p);
418 
419 	if (error)
420 		goto out;
421 
422 	/* Put the result into user buffer */
423 	error = copyoutstr(bp, SCARG(uap, buf), MAXPATHLEN, NULL);
424 
425 #ifdef KTRACE
426 	if (KTRPOINT(p, KTR_NAMEI))
427 		ktrnamei(p, bp);
428 #endif
429 
430 out:
431 	free(path, M_TEMP, len);
432 
433 	return (error);
434 }
435