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