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