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