xref: /openbsd-src/sys/kern/vfs_getcwd.c (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1 /* $OpenBSD: vfs_getcwd.c,v 1.26 2016/03/19 12:04:15 natano 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/proc.h>
43 #include <sys/uio.h>
44 #include <sys/malloc.h>
45 #include <sys/dirent.h>
46 #include <ufs/ufs/dir.h>	/* only for DIRBLKSIZ */
47 
48 #include <sys/syscallargs.h>
49 
50 
51 /* Find parent vnode of *lvpp, return in *uvpp */
52 int
53 vfs_getcwd_scandir(struct vnode **lvpp, struct vnode **uvpp, char **bpp,
54     char *bufp, struct proc *p)
55 {
56 	int eofflag, tries, dirbuflen, len, reclen, error = 0;
57 	off_t off;
58 	struct uio uio;
59 	struct iovec iov;
60 	char *dirbuf = NULL;
61 	ino_t fileno;
62 	struct vattr va;
63 	struct vnode *uvp = NULL;
64 	struct vnode *lvp = *lvpp;
65 	struct componentname cn;
66 
67 	tries = 0;
68 
69 	/*
70 	 * If we want the filename, get some info we need while the
71 	 * current directory is still locked.
72 	 */
73 	if (bufp != NULL) {
74 		error = VOP_GETATTR(lvp, &va, p->p_ucred, p);
75 		if (error) {
76 			vput(lvp);
77 			*lvpp = NULL;
78 			*uvpp = NULL;
79 			return (error);
80 		}
81 	}
82 
83 	cn.cn_nameiop = LOOKUP;
84 	cn.cn_flags = ISLASTCN | ISDOTDOT | RDONLY;
85 	cn.cn_proc = p;
86 	cn.cn_cred = p->p_ucred;
87 	cn.cn_pnbuf = NULL;
88 	cn.cn_nameptr = "..";
89 	cn.cn_namelen = 2;
90 	cn.cn_consume = 0;
91 
92 	/* Get parent vnode using lookup of '..' */
93 	error = VOP_LOOKUP(lvp, uvpp, &cn);
94 	if (error) {
95 		vput(lvp);
96 		*lvpp = NULL;
97 		*uvpp = NULL;
98 		return (error);
99 	}
100 
101 	uvp = *uvpp;
102 
103 	/* If we don't care about the pathname, we're done */
104 	if (bufp == NULL) {
105 		vrele(lvp);
106 		*lvpp = NULL;
107 		return (0);
108 	}
109 
110 	fileno = va.va_fileid;
111 
112 	dirbuflen = DIRBLKSIZ;
113 
114 	if (dirbuflen < va.va_blocksize)
115 		dirbuflen = va.va_blocksize;
116 
117 	dirbuf = malloc(dirbuflen, M_TEMP, M_WAITOK);
118 
119 	off = 0;
120 
121 	do {
122 		char   *cpos;
123 		struct dirent *dp;
124 
125 		iov.iov_base = dirbuf;
126 		iov.iov_len = dirbuflen;
127 
128 		uio.uio_iov = &iov;
129 		uio.uio_iovcnt = 1;
130 		uio.uio_offset = off;
131 		uio.uio_resid = dirbuflen;
132 		uio.uio_segflg = UIO_SYSSPACE;
133 		uio.uio_rw = UIO_READ;
134 		uio.uio_procp = p;
135 
136 		eofflag = 0;
137 
138 		/* Call VOP_READDIR of parent */
139 		error = VOP_READDIR(uvp, &uio, p->p_ucred, &eofflag);
140 
141 		off = uio.uio_offset;
142 
143 		/* Try again if NFS tosses its cookies */
144 		if (error == EINVAL && tries < 3) {
145 			tries++;
146 			off = 0;
147 			continue;
148 		} else if (error) {
149 			goto out; /* Old userland getcwd() behaviour */
150 		}
151 
152 		cpos = dirbuf;
153 		tries = 0;
154 
155 		/* Scan directory page looking for matching vnode */
156 		for (len = (dirbuflen - uio.uio_resid); len > 0;
157 		     len -= reclen) {
158 			dp = (struct dirent *)cpos;
159 			reclen = dp->d_reclen;
160 
161 			/* Check for malformed directory */
162 			if (reclen < DIRENT_RECSIZE(1)) {
163 				error = EINVAL;
164 				goto out;
165 			}
166 
167 			if (dp->d_fileno == fileno) {
168 				char *bp = *bpp;
169 				bp -= dp->d_namlen;
170 
171 				if (bp <= bufp) {
172 					error = ERANGE;
173 					goto out;
174 				}
175 
176 				memmove(bp, dp->d_name, dp->d_namlen);
177 				error = 0;
178 				*bpp = bp;
179 
180 				goto out;
181 			}
182 
183 			cpos += reclen;
184 		}
185 
186 	} while (!eofflag);
187 
188 	error = ENOENT;
189 
190 out:
191 
192 	vrele(lvp);
193 	*lvpp = NULL;
194 
195 	free(dirbuf, M_TEMP, dirbuflen);
196 
197 	return (error);
198 }
199 
200 /* Do a lookup in the vnode-to-name reverse */
201 int
202 vfs_getcwd_getcache(struct vnode **lvpp, struct vnode **uvpp, char **bpp,
203     char *bufp)
204 {
205 	struct vnode *lvp, *uvp = NULL;
206 	struct proc *p = curproc;
207 	char *obp;
208 	int error, vpid;
209 
210 	lvp = *lvpp;
211 	obp = *bpp;	/* Save original position to restore to on error */
212 
213 	error = cache_revlookup(lvp, uvpp, bpp, bufp);
214 	if (error) {
215 		if (error != -1) {
216 			vput(lvp);
217 			*lvpp = NULL;
218 			*uvpp = NULL;
219 		}
220 
221 		return (error);
222 	}
223 
224 	uvp = *uvpp;
225 	vpid = uvp->v_id;
226 
227 
228 	/* Release current lock before acquiring the parent lock */
229 	VOP_UNLOCK(lvp, p);
230 
231 	error = vget(uvp, LK_EXCLUSIVE | LK_RETRY, p);
232 	if (error)
233 		*uvpp = NULL;
234 
235 	/*
236 	 * Verify that vget() succeeded, and check that vnode capability
237 	 * didn't change while we were waiting for the lock.
238 	 */
239 	if (error || (vpid != uvp->v_id)) {
240 		/*
241 		 * Try to get our lock back. If that works, tell the caller to
242 		 * try things the hard way, otherwise give up.
243 		 */
244 		if (!error)
245 			vput(uvp);
246 
247 		*uvpp = NULL;
248 
249 		error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY, p);
250 		if (!error) {
251 			*bpp = obp; /* restore the buffer */
252 			return (-1);
253 		}
254 	}
255 
256 	vrele(lvp);
257 	*lvpp = NULL;
258 
259 	return (error);
260 }
261 
262 /* Common routine shared by sys___getcwd() and vn_isunder() */
263 int
264 vfs_getcwd_common(struct vnode *lvp, struct vnode *rvp, char **bpp, char *bufp,
265     int limit, int flags, struct proc *p)
266 {
267 	struct filedesc *fdp = p->p_fd;
268 	struct vnode *uvp = NULL;
269 	char *bp = NULL;
270 	int error, perms = VEXEC;
271 
272 	if (rvp == NULL) {
273 		rvp = fdp->fd_rdir;
274 		if (rvp == NULL)
275 			rvp = rootvnode;
276 	}
277 
278 	vref(rvp);
279 	vref(lvp);
280 
281 	error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY, p);
282 	if (error) {
283 		vrele(lvp);
284 		lvp = NULL;
285 		goto out;
286 	}
287 
288 	if (bufp)
289 		bp = *bpp;
290 
291 	if (lvp == rvp) {
292 		if (bp)
293 			*(--bp) = '/';
294 		goto out;
295 	}
296 
297 	/*
298 	 * This loop will terminate when we hit the root, VOP_READDIR() or
299 	 * VOP_LOOKUP() fails, or we run out of space in the user buffer.
300 	 */
301 	do {
302 		if (lvp->v_type != VDIR) {
303 			error = ENOTDIR;
304 			goto out;
305 		}
306 
307 		/* Check for access if caller cares */
308 		if (flags & GETCWD_CHECK_ACCESS) {
309 			error = VOP_ACCESS(lvp, perms, p->p_ucred, p);
310 			if (error)
311 				goto out;
312 			perms = VEXEC|VREAD;
313 		}
314 
315 		/* Step up if we're a covered vnode */
316 		while (lvp->v_flag & VROOT) {
317 			struct vnode *tvp;
318 
319 			if (lvp == rvp)
320 				goto out;
321 
322 			tvp = lvp;
323 			lvp = lvp->v_mount->mnt_vnodecovered;
324 
325 			vput(tvp);
326 
327 			if (lvp == NULL) {
328 				error = ENOENT;
329 				goto out;
330 			}
331 
332 			vref(lvp);
333 
334 			error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY, p);
335 			if (error) {
336 				vrele(lvp);
337 				lvp = NULL;
338 				goto out;
339 			}
340 		}
341 
342 		/* Look in the name cache */
343 		error = vfs_getcwd_getcache(&lvp, &uvp, &bp, bufp);
344 
345 		if (error == -1) {
346 			/* If that fails, look in the directory */
347 			error = vfs_getcwd_scandir(&lvp, &uvp, &bp, bufp, p);
348 		}
349 
350 		if (error)
351 			goto out;
352 
353 #ifdef DIAGNOSTIC
354 		if (lvp != NULL)
355 			panic("getcwd: oops, forgot to null lvp");
356 		if (bufp && (bp <= bufp)) {
357 			panic("getcwd: oops, went back too far");
358 		}
359 #endif
360 
361 		if (bp)
362 			*(--bp) = '/';
363 
364 		lvp = uvp;
365 		uvp = NULL;
366 		limit--;
367 
368 	} while ((lvp != rvp) && (limit > 0));
369 
370 out:
371 
372 	if (bpp)
373 		*bpp = bp;
374 
375 	if (uvp)
376 		vput(uvp);
377 
378 	if (lvp)
379 		vput(lvp);
380 
381 	vrele(rvp);
382 
383 	return (error);
384 }
385 
386 /* Find pathname of a process's current directory */
387 int
388 sys___getcwd(struct proc *p, void *v, register_t *retval)
389 {
390 	struct sys___getcwd_args *uap = v;
391 	int error, lenused, len = SCARG(uap, len);
392 	char *path, *bp, *bend;
393 
394 	if (len > MAXPATHLEN * 4)
395 		len = MAXPATHLEN * 4;
396 	else if (len < 2)
397 		return (ERANGE);
398 
399 	path = malloc(len, M_TEMP, M_WAITOK);
400 
401 	bp = &path[len];
402 	bend = bp;
403 	*(--bp) = '\0';
404 
405 	/*
406 	 * 5th argument here is "max number of vnodes to traverse".
407 	 * Since each entry takes up at least 2 bytes in the output
408 	 * buffer, limit it to N/2 vnodes for an N byte buffer.
409 	 */
410 	error = vfs_getcwd_common(p->p_fd->fd_cdir, NULL, &bp, path, len/2,
411 	    GETCWD_CHECK_ACCESS, p);
412 
413 	if (error)
414 		goto out;
415 
416 	lenused = bend - bp;
417 	*retval = lenused;
418 
419 	/* Put the result into user buffer */
420 	error = copyout(bp, SCARG(uap, buf), lenused);
421 
422 out:
423 	free(path, M_TEMP, len);
424 
425 	return (error);
426 }
427