vfs_getcwd.c revision 1.1 1 /* $NetBSD: vfs_getcwd.c,v 1.1 1999/03/22 17:01:55 sommerfe 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 * 3. All advertising materials mentioning features or use of this software
19 * must display the following acknowledgement:
20 * This product includes software developed by the NetBSD
21 * Foundation, Inc. and its contributors.
22 * 4. Neither the name of The NetBSD Foundation nor the names of its
23 * contributors may be used to endorse or promote products derived
24 * from this software without specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36 * POSSIBILITY OF SUCH DAMAGE.
37 */
38
39 #include <sys/param.h>
40 #include <sys/systm.h>
41 #include <sys/namei.h>
42 #include <sys/filedesc.h>
43 #include <sys/kernel.h>
44 #include <sys/file.h>
45 #include <sys/stat.h>
46 #include <sys/vnode.h>
47 #include <sys/mount.h>
48 #include <sys/proc.h>
49 #include <sys/uio.h>
50 #include <sys/malloc.h>
51 #include <sys/dirent.h>
52 #include <ufs/ufs/dir.h> /* XXX only for DIRBLKSIZ */
53
54 #include <sys/syscallargs.h>
55
56 static int
57 getcwd_scandir __P((struct vnode *, struct vnode **,
58 char **, char *, struct proc *));
59 static int
60 getcwd_getcache __P((struct vnode **, struct vnode **,
61 char **, char *));
62 static int
63 getcwd_common __P((struct vnode *, struct vnode *,
64 char **, char *, int, int, struct proc *));
65
66 int vn_isunder __P((struct vnode *, struct vnode *, struct proc *));
67
68 #define DIRENT_MINSIZE (sizeof(struct dirent) - (MAXNAMLEN+1) + 4)
69
70 /*
71 * XXX Will infinite loop in certain cases if a directory read reliably
72 * returns EINVAL on last block.
73 * XXX is EINVAL the right thing to return if a directory is malformed?
74 */
75
76 /*
77 * Find parent vnode of cvp, return in *pvpp
78 * Scan it looking for name of directory entry pointing at cvp.
79 *
80 * Place the name in the buffer which starts at bufp, immediately
81 * before *bpp, and move bpp backwards to point at the start of it.
82 */
83 static int
84 getcwd_scandir(cvp, pvpp, bpp, bufp, p)
85 struct vnode *cvp;
86 struct vnode **pvpp;
87 char **bpp;
88 char *bufp;
89 struct proc *p;
90 {
91 char *entry;
92 int error = 0;
93 int eofflag;
94 off_t off;
95 int tries;
96 struct uio uio;
97 struct iovec iov;
98 char *dirbuf = NULL;
99 int dirbuflen;
100 ino_t fileno;
101 struct vattr va;
102 struct vnode *pvp = NULL;
103 struct componentname cn;
104 int len, reclen;
105 tries = 0;
106
107 /*
108 * Ok, we have to do it the hard way..
109 * First, get parent vnode using lookup of ..
110 */
111 cn.cn_nameiop = LOOKUP;
112 cn.cn_flags = ISLASTCN | ISDOTDOT | RDONLY | LOCKPARENT;
113 cn.cn_proc = p;
114 cn.cn_cred = p->p_ucred;
115 cn.cn_pnbuf = NULL;
116 cn.cn_nameptr = "..";
117 cn.cn_namelen = 2;
118 cn.cn_hash = 0;
119 cn.cn_consume = 0;
120
121 /*
122 * At this point, cvp is locked, and will be locked
123 * on return in all cases.
124 * On successful return, *pvpp will also be locked
125 */
126 error = VOP_LOOKUP(cvp, pvpp, &cn);
127 if (error) {
128 *pvpp = NULL;
129 return error;
130 }
131 pvp = *pvpp;
132
133 /* If we don't care about the pathname, we're done */
134 if (bufp == NULL)
135 return 0;
136
137 error = VOP_GETATTR(cvp, &va, p->p_ucred, p);
138 if (error)
139 return error;
140 fileno = va.va_fileid;
141
142
143 dirbuflen = DIRBLKSIZ;
144 if (dirbuflen < va.va_blocksize)
145 dirbuflen = va.va_blocksize;
146 dirbuf = (char *)malloc(dirbuflen, M_TEMP, M_WAITOK);
147
148 #if 0
149 unionread:
150 #endif
151 off = 0;
152 do {
153 /* call VOP_READDIR of parent */
154 iov.iov_base = dirbuf;
155 iov.iov_len = dirbuflen;
156
157 uio.uio_iov = &iov;
158 uio.uio_iovcnt = 1;
159 uio.uio_offset = off;
160 uio.uio_resid = dirbuflen;
161 uio.uio_segflg = UIO_SYSSPACE;
162 uio.uio_rw = UIO_READ;
163 uio.uio_procp = p;
164
165 eofflag = 0;
166
167 error = VOP_READDIR(pvp, &uio, p->p_ucred, &eofflag, 0, 0);
168
169 off = uio.uio_offset;
170
171 /*
172 * Try again if NFS tosses its cookies.
173 * XXX this can still loop forever if the directory is busted
174 * such that the second or subsequent page of it always
175 * returns EINVAL
176 */
177 if ((error == EINVAL) && (tries < 3)) {
178 off = 0;
179 tries++;
180 continue; /* once more, with feeling */
181 }
182 entry = NULL;
183 if (!error) {
184 char *cpos;
185 struct dirent *dp;
186
187 cpos = dirbuf;
188 tries = 0;
189
190 /* scan directory page looking for matching vnode */
191 for (len = (dirbuflen - uio.uio_resid); len > 0; len -= reclen) {
192 dp = (struct dirent *) cpos;
193 reclen = dp->d_reclen;
194
195 /* check for malformed directory.. */
196 if (reclen < DIRENT_MINSIZE) {
197 error = EINVAL;
198 goto out;
199 }
200 /*
201 * XXX should perhaps do VOP_LOOKUP to
202 * check that we got back to the right place,
203 * but getting the locking games for that
204 * right would be heinous.
205 */
206 if ((dp->d_type != DT_WHT) &&
207 (dp->d_fileno == fileno)) {
208 char *bp = *bpp;
209 bp -= dp->d_namlen;
210
211 if (bp <= bufp) {
212 error = ERANGE;
213 goto out;
214 }
215 memcpy(bp, dp->d_name, dp->d_namlen);
216 error = 0;
217 *bpp = bp;
218 goto out;
219 }
220 cpos += reclen;
221 }
222 }
223 } while (!eofflag && !entry);
224 #if 0
225 /*
226 * Deal with mount -o union, which unions only the
227 * root directory of the mount.
228 */
229 if ((pvp->v_flag & VROOT) &&
230 (pvp->v_mount->mnt_flag & MNT_UNION)) {
231 struct vnode *tvp = pvp;
232 pvp = pvp->v_mount->mnt_vnodecovered;
233 vput(tvp);
234 VREF(pvp);
235 *pvpp = pvp;
236 error = vn_lock(pvp, LK_EXCLUSIVE | LK_RETRY);
237 if (error != 0) {
238 vrele(pvp);
239 *pvpp = pvp = NULL;
240 goto out;
241 }
242 goto unionread;
243 }
244 #endif
245 error = ENOENT;
246
247 out:
248 free(dirbuf, M_TEMP);
249 return error;
250 }
251
252 /*
253 * Look in the vnode-to-name reverse cache to see if
254 * we can find things the easy way.
255 *
256 * XXX vn_lock/vget failure paths are untested.
257 */
258
259 static int
260 getcwd_getcache(vpp, dvpp, bpp, bufp)
261 struct vnode **vpp, **dvpp;
262 char **bpp;
263 char *bufp;
264 {
265 struct vnode *cvp, *pvp = NULL;
266 int error;
267
268 cvp = *vpp;
269
270 error = cache_revlookup(cvp, dvpp, bpp, bufp);
271 if (error)
272 return error;
273 pvp = *dvpp;
274
275 /*
276 * Do a little dance with the locks to avoid deadlocking
277 * someone going the other way. Since we're going up, we have
278 * to release the current lock before we take the parent lock,
279 * and then re-take the current lock. Since either lock can
280 * fail, causing us to abort, this is a little convoluted.
281 */
282
283 VOP_UNLOCK(cvp, 0);
284 /* cur now unlocked... */
285 error = vget(pvp, LK_EXCLUSIVE | LK_RETRY);
286 if (error != 0) {
287 vrele(cvp);
288 *vpp = NULL;
289 *dvpp = NULL;
290 return error;
291 }
292 /* parent is now locked */
293 error = vn_lock(cvp, LK_EXCLUSIVE | LK_RETRY);
294 if (error != 0) {
295 vrele(cvp);
296 *vpp = NULL;
297 return error;
298 }
299 /* ok, cur is now locked again.. */
300 return 0;
301 }
302
303 /*
304 * common routine shared by sys___getcwd() and vn_isunder()
305 */
306
307 #define GETCWD_CHECK_ACCESS 0x0001
308
309 static int getcwd_common (dvp, rvp, bpp, bufp, limit, flags, p)
310 struct vnode *dvp;
311 struct vnode *rvp;
312 char **bpp;
313 char *bufp;
314 int limit;
315 int flags;
316 struct proc *p;
317 {
318 struct filedesc *fdp = p->p_fd;
319 struct vnode *pvp = NULL;
320 char *bp;
321 int error;
322
323 if (rvp == NULL) {
324 rvp = fdp->fd_rdir;
325 if (rvp == NULL)
326 rvp = rootvnode;
327 }
328
329 VREF(rvp);
330 VREF(dvp);
331
332 /*
333 * Error handling invariant:
334 * Before a `goto out':
335 * dvp is either NULL, or locked and held.
336 * pvp is either NULL, or locked and held.
337 */
338
339 error = vn_lock(dvp, LK_EXCLUSIVE | LK_RETRY);
340 if (error) {
341 vrele(dvp);
342 dvp = NULL;
343 goto out;
344 }
345 if (bufp)
346 bp = *bpp;
347 /*
348 * this loop will terminate when one of the following happens:
349 * - we hit the root
350 * - getdirentries or lookup fails
351 * - we run out of space in the buffer.
352 */
353 if (dvp == rvp) {
354 if (bufp)
355 *(--(*bpp)) = '/';
356 goto out;
357 }
358 do {
359 if (dvp->v_type != VDIR) {
360 error = ENOTDIR;
361 goto out;
362 }
363
364 /*
365 * access check here is optional, depending on
366 * whether or not caller cares.
367 */
368 if (flags & GETCWD_CHECK_ACCESS) {
369 error = VOP_ACCESS(dvp, VEXEC|VREAD, p->p_ucred, p);
370 if (error)
371 goto out;
372 }
373
374 /*
375 * step up if we're a covered vnode..
376 */
377 while (dvp->v_flag & VROOT) {
378 struct vnode *tvp;
379
380 if (dvp == rvp)
381 goto out;
382
383 tvp = dvp;
384 dvp = dvp->v_mount->mnt_vnodecovered;
385 vput(tvp);
386 /*
387 * hodie natus est radici frater
388 */
389 if (dvp == NULL) {
390 error = ENOENT;
391 goto out;
392 }
393 VREF(dvp);
394 error = vn_lock(dvp, LK_EXCLUSIVE | LK_RETRY);
395 if (error != 0) {
396 vrele(dvp);
397 dvp = NULL;
398 goto out;
399 }
400 }
401 /*
402 * Look in the name cache; if that fails, look in the
403 * directory..
404 */
405 error = getcwd_getcache(&dvp, &pvp, bpp, bufp);
406 if (error == -1)
407 error = getcwd_scandir(dvp, &pvp, bpp, bufp, p);
408 if (error)
409 goto out;
410 #if DIAGNOSTIC
411 if (dvp == pvp) {
412 panic("getcwd: oops, dvp == pvp");
413 }
414 if (bufp && (bp <= bufp)) {
415 panic("getcwd: oops, went back too far");
416 }
417 #endif
418 if (bufp) *(--(*bpp)) = '/';
419
420 vput(dvp);
421 dvp = pvp;
422 pvp = NULL;
423 limit--;
424 } while ((dvp != rvp) && (limit > 0));
425
426 out:
427 if (pvp)
428 vput(pvp);
429 if (dvp)
430 vput(dvp);
431 vrele(rvp);
432 return error;
433 }
434
435 /*
436 * Check if one directory can be found inside another in the directory
437 * hierarchy.
438 *
439 * Intended to be used in chroot, chdir, fchdir, etc., to ensure that
440 * chroot() actually means something.
441 */
442 int vn_isunder(dvp, rvp, p)
443 struct vnode *dvp;
444 struct vnode *rvp;
445 struct proc *p;
446 {
447 int error;
448
449 error = getcwd_common (dvp, rvp, NULL, NULL, MAXPATHLEN/2, 0, p);
450
451 if (!error)
452 return 1;
453 else
454 return 0;
455 }
456
457 int sys___getcwd(p, v, retval)
458 struct proc *p;
459 void *v;
460 register_t *retval;
461 {
462 register struct sys___getcwd_args /* {
463 syscallarg(char *) bufp;
464 syscallarg(size_t) length;
465 } */ *uap = v;
466
467 int error;
468 char *path;
469 char *bp, *bend;
470 int len = SCARG(uap, length);
471 int lenused;
472
473 if ((len < 2) || (len > MAXPATHLEN*4))
474 return ERANGE;
475
476 path = (char *)malloc(len, M_TEMP, M_WAITOK);
477 if (!path)
478 return ENOMEM;
479
480 bp = &path[len];
481 bend = bp;
482 *(--bp) = '\0';
483
484 error = getcwd_common (p->p_fd->fd_cdir, NULL, &bp, path, len/2,
485 GETCWD_CHECK_ACCESS, p);
486
487 if (error)
488 goto out;
489 lenused = bend - bp;
490 *retval = lenused;
491 /* put the result into user buffer */
492 error = copyout(bp, SCARG(uap, bufp), lenused);
493
494 out:
495 free(path, M_TEMP);
496 return error;
497 }
498
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 /*
509 * XXX Untested vs. mount -o union; probably does the wrong thing.
510 * XXX Untested vs chroot
511 * XXX most error paths probably work, but many locking-related ones
512 * aren't tested well.
513 */
514 #if 0
515
516 int
517 sys___getcwd(p, v, retval)
518 struct proc *p;
519 void *v;
520 register_t *retval;
521 {
522 register struct sys___getcwd_args /* {
523 syscallarg(char *) bufp;
524 syscallarg(size_t) length;
525 } */ *uap = v;
526
527 struct filedesc *fdp = p->p_fd;
528 struct vnode *cvp = NULL, *pvp = NULL, *rootvp = NULL;
529 int error;
530 char *path;
531 char *bp, *bend;
532 int len = SCARG(uap, length);
533 int lenused;
534
535 if ((len < 2) || (len > MAXPATHLEN*4))
536 return ERANGE;
537
538 path = (char *)malloc(len, M_TEMP, M_WAITOK);
539 if (!path)
540 return ENOMEM;
541
542 bp = &path[len];
543 bend = bp;
544 *(--bp) = '\0';
545
546 rootvp = fdp->fd_rdir;
547 if (rootvp == NULL)
548 rootvp = rootvnode;
549
550 cvp = fdp->fd_cdir;
551
552 VREF(rootvp);
553 VREF(cvp);
554
555 /*
556 * Error handling invariant:
557 * Before a `goto out':
558 * cvp is either NULL, or locked and held.
559 * pvp is either NULL, or locked and held.
560 */
561
562 error = vn_lock(cvp, LK_EXCLUSIVE | LK_RETRY);
563 if (error) {
564 vrele(cvp);
565 cvp = NULL;
566 goto out;
567 }
568 /*
569 * this loop will terminate when one of the following happens:
570 * - we hit the root
571 * - getdirentries or lookup fails
572 * - we run out of space in the buffer.
573 */
574 if (cvp == rootvp) {
575 *(--bp) = '/';
576 goto hitroot;
577 }
578 do {
579 /*
580 * so, are we even allowed to look at this directory?
581 */
582
583 error = VOP_ACCESS(cvp, VEXEC|VREAD, p->p_ucred, p);
584 if (error)
585 goto out;
586
587 /*
588 * step up if we're a covered vnode..
589 */
590 while (cvp->v_flag & VROOT) {
591 struct vnode *tvp;
592
593 if (cvp == rootvp)
594 goto hitroot;
595
596 tvp = cvp;
597 cvp = cvp->v_mount->mnt_vnodecovered;
598 vput(tvp);
599 VREF(cvp);
600 error = vn_lock(cvp, LK_EXCLUSIVE | LK_RETRY);
601 if (error != 0) {
602 vrele(cvp);
603 cvp = NULL;
604 goto out;
605 }
606 }
607 /*
608 * Look in the name cache; if that fails, look in the directory..
609 */
610 error = getcwd_getcache(&cvp, &pvp, &bp, path);
611 if (error == -1)
612 error = getcwd_scandir(cvp, &pvp, &bp, path, p);
613
614 if (error)
615 goto out;
616 if (bp <= path) {
617 error = ERANGE;
618 goto out;
619 }
620 *(--bp) = '/';
621
622 vput(cvp);
623 cvp = pvp;
624 pvp = NULL;
625
626 } while (cvp != rootvp);
627 hitroot:
628
629 lenused = bend - bp;
630 *retval = lenused;
631 /* put the result into user buffer */
632 error = copyout(bp, SCARG(uap, bufp), lenused);
633
634 out:
635 if (pvp)
636 vput(pvp);
637 if (cvp)
638 vput(cvp);
639 vrele(rootvp);
640 free(path, M_TEMP);
641 return error;
642 }
643 #endif
644