vfs_getcwd.c revision 1.42.2.1 1 /* $NetBSD: vfs_getcwd.c,v 1.42.2.1 2008/05/10 23:49:05 wrstuden Exp $ */
2
3 /*-
4 * Copyright (c) 1999 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Bill Sommerfeld.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 #include <sys/cdefs.h>
33 __KERNEL_RCSID(0, "$NetBSD: vfs_getcwd.c,v 1.42.2.1 2008/05/10 23:49:05 wrstuden Exp $");
34
35 #include <sys/param.h>
36 #include <sys/systm.h>
37 #include <sys/namei.h>
38 #include <sys/filedesc.h>
39 #include <sys/kernel.h>
40 #include <sys/file.h>
41 #include <sys/stat.h>
42 #include <sys/vnode.h>
43 #include <sys/mount.h>
44 #include <sys/proc.h>
45 #include <sys/uio.h>
46 #include <sys/malloc.h>
47 #include <sys/dirent.h>
48 #include <sys/kauth.h>
49
50 #include <ufs/ufs/dir.h> /* XXX only for DIRBLKSIZ */
51
52 #include <sys/sa.h>
53 #include <sys/syscallargs.h>
54
55 /*
56 * Vnode variable naming conventions in this file:
57 *
58 * rvp: the current root we're aiming towards.
59 * lvp, *lvpp: the "lower" vnode
60 * uvp, *uvpp: the "upper" vnode.
61 *
62 * Since all the vnodes we're dealing with are directories, and the
63 * lookups are going *up* in the filesystem rather than *down*, the
64 * usual "pvp" (parent) or "dvp" (directory) naming conventions are
65 * too confusing.
66 */
67
68 /*
69 * XXX Will infinite loop in certain cases if a directory read reliably
70 * returns EINVAL on last block.
71 * XXX is EINVAL the right thing to return if a directory is malformed?
72 */
73
74 /*
75 * XXX Untested vs. mount -o union; probably does the wrong thing.
76 */
77
78 /*
79 * Find parent vnode of *lvpp, return in *uvpp
80 *
81 * If we care about the name, scan it looking for name of directory
82 * entry pointing at lvp.
83 *
84 * Place the name in the buffer which starts at bufp, immediately
85 * before *bpp, and move bpp backwards to point at the start of it.
86 *
87 * On entry, *lvpp is a locked vnode reference; on exit, it is vput and NULL'ed
88 * On exit, *uvpp is either NULL or is a locked vnode reference.
89 */
90 static int
91 getcwd_scandir(struct vnode **lvpp, struct vnode **uvpp, char **bpp,
92 char *bufp, struct lwp *l)
93 {
94 int error = 0;
95 int eofflag;
96 off_t off;
97 int tries;
98 struct uio uio;
99 struct iovec iov;
100 char *dirbuf = NULL;
101 int dirbuflen;
102 ino_t fileno;
103 struct vattr va;
104 struct vnode *uvp = NULL;
105 struct vnode *lvp = *lvpp;
106 kauth_cred_t cred = l->l_cred;
107 struct componentname cn;
108 int len, reclen;
109 tries = 0;
110
111 /*
112 * If we want the filename, get some info we need while the
113 * current directory is still locked.
114 */
115 if (bufp != NULL) {
116 error = VOP_GETATTR(lvp, &va, cred);
117 if (error) {
118 vput(lvp);
119 *lvpp = NULL;
120 *uvpp = NULL;
121 return error;
122 }
123 }
124
125 /*
126 * Ok, we have to do it the hard way..
127 * Next, get parent vnode using lookup of ..
128 */
129 cn.cn_nameiop = LOOKUP;
130 cn.cn_flags = ISLASTCN | ISDOTDOT | RDONLY;
131 cn.cn_cred = cred;
132 cn.cn_pnbuf = NULL;
133 cn.cn_nameptr = "..";
134 cn.cn_namelen = 2;
135 cn.cn_hash = 0;
136 cn.cn_consume = 0;
137
138 /*
139 * At this point, lvp is locked.
140 * On successful return, *uvpp will be locked
141 */
142 error = VOP_LOOKUP(lvp, uvpp, &cn);
143 vput(lvp);
144 if (error) {
145 *lvpp = NULL;
146 *uvpp = NULL;
147 return error;
148 }
149 uvp = *uvpp;
150
151 /* If we don't care about the pathname, we're done */
152 if (bufp == NULL) {
153 *lvpp = NULL;
154 return 0;
155 }
156
157 fileno = va.va_fileid;
158
159 dirbuflen = DIRBLKSIZ;
160 if (dirbuflen < va.va_blocksize)
161 dirbuflen = va.va_blocksize;
162 dirbuf = (char *)malloc(dirbuflen, M_TEMP, M_WAITOK);
163
164 #if 0
165 unionread:
166 #endif
167 off = 0;
168 do {
169 /* call VOP_READDIR of parent */
170 iov.iov_base = dirbuf;
171 iov.iov_len = dirbuflen;
172
173 uio.uio_iov = &iov;
174 uio.uio_iovcnt = 1;
175 uio.uio_offset = off;
176 uio.uio_resid = dirbuflen;
177 uio.uio_rw = UIO_READ;
178 UIO_SETUP_SYSSPACE(&uio);
179
180 eofflag = 0;
181
182 error = VOP_READDIR(uvp, &uio, cred, &eofflag, 0, 0);
183
184 off = uio.uio_offset;
185
186 /*
187 * Try again if NFS tosses its cookies.
188 * XXX this can still loop forever if the directory is busted
189 * such that the second or subsequent page of it always
190 * returns EINVAL
191 */
192 if ((error == EINVAL) && (tries < 3)) {
193 off = 0;
194 tries++;
195 continue; /* once more, with feeling */
196 }
197
198 if (!error) {
199 char *cpos;
200 struct dirent *dp;
201
202 cpos = dirbuf;
203 tries = 0;
204
205 /* scan directory page looking for matching vnode */
206 for (len = (dirbuflen - uio.uio_resid); len > 0;
207 len -= reclen) {
208 dp = (struct dirent *) cpos;
209 reclen = dp->d_reclen;
210
211 /* check for malformed directory.. */
212 if (reclen < _DIRENT_MINSIZE(dp)) {
213 error = EINVAL;
214 goto out;
215 }
216 /*
217 * XXX should perhaps do VOP_LOOKUP to
218 * check that we got back to the right place,
219 * but getting the locking games for that
220 * right would be heinous.
221 */
222 if ((dp->d_type != DT_WHT) &&
223 (dp->d_fileno == fileno)) {
224 char *bp = *bpp;
225
226 bp -= dp->d_namlen;
227 if (bp <= bufp) {
228 error = ERANGE;
229 goto out;
230 }
231 memcpy(bp, dp->d_name, dp->d_namlen);
232 error = 0;
233 *bpp = bp;
234 goto out;
235 }
236 cpos += reclen;
237 }
238 } else
239 goto out;
240 } while (!eofflag);
241 #if 0
242 /*
243 * Deal with mount -o union, which unions only the
244 * root directory of the mount.
245 */
246 if ((uvp->v_vflag & VV_ROOT) &&
247 (uvp->v_mount->mnt_flag & MNT_UNION)) {
248 struct vnode *tvp = uvp;
249
250 uvp = uvp->v_mount->mnt_vnodecovered;
251 vput(tvp);
252 VREF(uvp);
253 *uvpp = uvp;
254 vn_lock(uvp, LK_EXCLUSIVE | LK_RETRY);
255 goto unionread;
256 }
257 #endif
258 error = ENOENT;
259
260 out:
261 *lvpp = NULL;
262 free(dirbuf, M_TEMP);
263 return error;
264 }
265
266 /*
267 * Look in the vnode-to-name reverse cache to see if
268 * we can find things the easy way.
269 *
270 * XXX vget failure path is untested.
271 *
272 * On entry, *lvpp is a locked vnode reference.
273 * On exit, one of the following is the case:
274 * 0) Both *lvpp and *uvpp are NULL and failure is returned.
275 * 1) *uvpp is NULL, *lvpp remains locked and -1 is returned (cache miss)
276 * 2) *uvpp is a locked vnode reference, *lvpp is vput and NULL'ed
277 * and 0 is returned (cache hit)
278 */
279
280 static int
281 getcwd_getcache(struct vnode **lvpp, struct vnode **uvpp, char **bpp,
282 char *bufp)
283 {
284 struct vnode *lvp, *uvp = NULL;
285 char *obp = *bpp;
286 int error;
287
288 lvp = *lvpp;
289
290 /*
291 * This returns 0 on a cache hit, -1 on a clean cache miss,
292 * or an errno on other failure.
293 */
294 error = cache_revlookup(lvp, uvpp, bpp, bufp);
295 if (error) {
296 if (error != -1) {
297 vput(lvp);
298 *lvpp = NULL;
299 *uvpp = NULL;
300 }
301 return error;
302 }
303 uvp = *uvpp;
304
305 /*
306 * Since we're going up, we have to release the current lock
307 * before we take the parent lock.
308 */
309
310 VOP_UNLOCK(lvp, 0);
311 error = vget(uvp, LK_EXCLUSIVE | LK_RETRY);
312
313 /*
314 * Verify that vget succeeded while we were waiting for the
315 * lock.
316 */
317 if (error) {
318
319 /*
320 * Oops, we missed. If the vget failed, get our lock back
321 * then rewind the `bp' and tell the caller to try things
322 * the hard way.
323 */
324 *uvpp = NULL;
325 vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY);
326 *bpp = obp;
327 return -1;
328 }
329 vrele(lvp);
330 *lvpp = NULL;
331
332 return error;
333 }
334
335 /*
336 * common routine shared by sys___getcwd() and vn_isunder()
337 */
338
339 int
340 getcwd_common(struct vnode *lvp, struct vnode *rvp, char **bpp, char *bufp,
341 int limit, int flags, struct lwp *l)
342 {
343 struct cwdinfo *cwdi = l->l_proc->p_cwdi;
344 kauth_cred_t cred = l->l_cred;
345 struct vnode *uvp = NULL;
346 char *bp = NULL;
347 int error;
348 int perms = VEXEC;
349
350 error = 0;
351 if (rvp == NULL) {
352 rvp = cwdi->cwdi_rdir;
353 if (rvp == NULL)
354 rvp = rootvnode;
355 }
356
357 VREF(rvp);
358 VREF(lvp);
359
360 /*
361 * Error handling invariant:
362 * Before a `goto out':
363 * lvp is either NULL, or locked and held.
364 * uvp is either NULL, or locked and held.
365 */
366
367 vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY);
368 if (bufp)
369 bp = *bpp;
370
371 /*
372 * this loop will terminate when one of the following happens:
373 * - we hit the root
374 * - getdirentries or lookup fails
375 * - we run out of space in the buffer.
376 */
377 if (lvp == rvp) {
378 if (bp)
379 *(--bp) = '/';
380 goto out;
381 }
382 do {
383 /*
384 * access check here is optional, depending on
385 * whether or not caller cares.
386 */
387 if (flags & GETCWD_CHECK_ACCESS) {
388 error = VOP_ACCESS(lvp, perms, cred);
389 if (error)
390 goto out;
391 perms = VEXEC|VREAD;
392 }
393
394 /*
395 * step up if we're a covered vnode..
396 */
397 while (lvp->v_vflag & VV_ROOT) {
398 struct vnode *tvp;
399
400 if (lvp == rvp)
401 goto out;
402
403 tvp = lvp;
404 lvp = lvp->v_mount->mnt_vnodecovered;
405 vput(tvp);
406 /*
407 * hodie natus est radici frater
408 */
409 if (lvp == NULL) {
410 error = ENOENT;
411 goto out;
412 }
413 VREF(lvp);
414 error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY);
415 if (error != 0) {
416 vrele(lvp);
417 lvp = NULL;
418 goto out;
419 }
420 }
421 /*
422 * Look in the name cache; if that fails, look in the
423 * directory..
424 */
425 error = getcwd_getcache(&lvp, &uvp, &bp, bufp);
426 if (error == -1) {
427 if (lvp->v_type != VDIR) {
428 error = ENOTDIR;
429 goto out;
430 }
431 error = getcwd_scandir(&lvp, &uvp, &bp, bufp, l);
432 }
433 if (error)
434 goto out;
435 #if DIAGNOSTIC
436 if (lvp != NULL)
437 panic("getcwd: oops, forgot to null lvp");
438 if (bufp && (bp <= bufp)) {
439 panic("getcwd: oops, went back too far");
440 }
441 #endif
442 if (bp)
443 *(--bp) = '/';
444 lvp = uvp;
445 uvp = NULL;
446 limit--;
447 } while ((lvp != rvp) && (limit > 0));
448
449 out:
450 if (bpp)
451 *bpp = bp;
452 if (uvp)
453 vput(uvp);
454 if (lvp)
455 vput(lvp);
456 vrele(rvp);
457 return error;
458 }
459
460 /*
461 * Check if one directory can be found inside another in the directory
462 * hierarchy.
463 *
464 * Intended to be used in chroot, chdir, fchdir, etc., to ensure that
465 * chroot() actually means something.
466 */
467 int
468 vn_isunder(struct vnode *lvp, struct vnode *rvp, struct lwp *l)
469 {
470 int error;
471
472 error = getcwd_common(lvp, rvp, NULL, NULL, MAXPATHLEN / 2, 0, l);
473
474 if (!error)
475 return 1;
476 else
477 return 0;
478 }
479
480 /*
481 * Returns true if proc p1's root directory equal to or under p2's
482 * root directory.
483 *
484 * Intended to be used from ptrace/procfs sorts of things.
485 */
486
487 int
488 proc_isunder(struct proc *p1, struct lwp *l2)
489 {
490 struct vnode *r1 = p1->p_cwdi->cwdi_rdir;
491 struct vnode *r2 = l2->l_proc->p_cwdi->cwdi_rdir;
492
493 if (r1 == NULL)
494 return (r2 == NULL);
495 else if (r2 == NULL)
496 return 1;
497 else
498 return vn_isunder(r1, r2, l2);
499 }
500
501 /*
502 * Find pathname of process's current directory.
503 *
504 * Use vfs vnode-to-name reverse cache; if that fails, fall back
505 * to reading directory contents.
506 */
507
508 int
509 sys___getcwd(struct lwp *l, const struct sys___getcwd_args *uap, register_t *retval)
510 {
511 /* {
512 syscallarg(char *) bufp;
513 syscallarg(size_t) length;
514 } */
515
516 int error;
517 char *path;
518 char *bp, *bend;
519 int len = SCARG(uap, length);
520 int lenused;
521 struct cwdinfo *cwdi;
522
523 if (len > MAXPATHLEN * 4)
524 len = MAXPATHLEN * 4;
525 else if (len < 2)
526 return ERANGE;
527
528 path = (char *)malloc(len, M_TEMP, M_WAITOK);
529 if (!path)
530 return ENOMEM;
531
532 bp = &path[len];
533 bend = bp;
534 *(--bp) = '\0';
535
536 /*
537 * 5th argument here is "max number of vnodes to traverse".
538 * Since each entry takes up at least 2 bytes in the output buffer,
539 * limit it to N/2 vnodes for an N byte buffer.
540 */
541 cwdi = l->l_proc->p_cwdi;
542 rw_enter(&cwdi->cwdi_lock, RW_READER);
543 error = getcwd_common(cwdi->cwdi_cdir, NULL, &bp, path,
544 len/2, GETCWD_CHECK_ACCESS, l);
545 rw_exit(&cwdi->cwdi_lock);
546
547 if (error)
548 goto out;
549 lenused = bend - bp;
550 *retval = lenused;
551 /* put the result into user buffer */
552 error = copyout(bp, SCARG(uap, bufp), lenused);
553
554 out:
555 free(path, M_TEMP);
556 return error;
557 }
558
559 /*
560 * Try to find a pathname for a vnode. Since there is no mapping
561 * vnode -> parent directory, this needs the NAMECACHE_ENTER_REVERSE
562 * option to work (to make cache_revlookup succeed).
563 */
564 int
565 vnode_to_path(char *path, size_t len, struct vnode *vp, struct lwp *curl,
566 struct proc *p)
567 {
568 struct proc *curp = curl->l_proc;
569 int error, lenused, elen;
570 char *bp, *bend;
571 struct vnode *dvp;
572
573 bp = bend = &path[len];
574 *(--bp) = '\0';
575
576 error = vget(vp, LK_EXCLUSIVE | LK_RETRY);
577 if (error != 0)
578 return error;
579 error = cache_revlookup(vp, &dvp, &bp, path);
580 vput(vp);
581 if (error != 0)
582 return (error == -1 ? ENOENT : error);
583
584 error = vget(dvp, 0);
585 if (error != 0)
586 return error;
587 *(--bp) = '/';
588 /* XXX GETCWD_CHECK_ACCESS == 0x0001 */
589 error = getcwd_common(dvp, NULL, &bp, path, len / 2, 1, curl);
590
591 /*
592 * Strip off emulation path for emulated processes looking at
593 * the maps file of a process of the same emulation. (Won't
594 * work if /emul/xxx is a symlink..)
595 */
596 if (curp->p_emul == p->p_emul && curp->p_emul->e_path != NULL) {
597 elen = strlen(curp->p_emul->e_path);
598 if (!strncmp(bp, curp->p_emul->e_path, elen))
599 bp = &bp[elen];
600 }
601
602 lenused = bend - bp;
603
604 memcpy(path, bp, lenused);
605 path[lenused] = 0;
606
607 return 0;
608 }
609