1 /* $OpenBSD: vfs_getcwd.c,v 1.38 2022/12/05 23:18:37 deraadt 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/stat.h> 38 #include <sys/lock.h> 39 #include <sys/vnode.h> 40 #include <sys/mount.h> 41 #include <sys/ktrace.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 51 /* Find parent vnode of *lvpp, return in *uvpp */ 52 int 53 vfs_getcwd_scandir(struct vnode **lvpp, struct vnode **uvpp, char **bpp, 54 char *bufp, struct proc *p) 55 { 56 int eofflag, tries, dirbuflen = 0, len, reclen, error = 0; 57 off_t off; 58 struct uio uio; 59 struct iovec iov; 60 char *dirbuf = NULL; 61 ino_t fileno; 62 struct vattr va; 63 struct vnode *uvp = NULL; 64 struct vnode *lvp = *lvpp; 65 struct componentname cn; 66 67 tries = 0; 68 69 /* 70 * If we want the filename, get some info we need while the 71 * current directory is still locked. 72 */ 73 if (bufp != NULL) { 74 error = VOP_GETATTR(lvp, &va, p->p_ucred, p); 75 if (error) { 76 vput(lvp); 77 *lvpp = NULL; 78 *uvpp = NULL; 79 return (error); 80 } 81 } 82 83 cn.cn_nameiop = LOOKUP; 84 cn.cn_flags = ISLASTCN | ISDOTDOT | RDONLY; 85 cn.cn_proc = p; 86 cn.cn_cred = p->p_ucred; 87 cn.cn_pnbuf = NULL; 88 cn.cn_nameptr = ".."; 89 cn.cn_namelen = 2; 90 cn.cn_consume = 0; 91 92 /* Get parent vnode using lookup of '..' */ 93 error = VOP_LOOKUP(lvp, uvpp, &cn); 94 if (error) { 95 vput(lvp); 96 *lvpp = NULL; 97 *uvpp = NULL; 98 return (error); 99 } 100 101 uvp = *uvpp; 102 103 /* If we don't care about the pathname, we're done */ 104 if (bufp == NULL) { 105 error = 0; 106 goto out; 107 } 108 109 fileno = va.va_fileid; 110 111 dirbuflen = DIRBLKSIZ; 112 if (dirbuflen < va.va_blocksize) 113 dirbuflen = va.va_blocksize; 114 /* XXX we need some limit for fuse, 1 MB should be enough */ 115 if (dirbuflen > 0xfffff) { 116 error = EINVAL; 117 goto out; 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); 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_RECSIZE(1) || reclen > len) { 165 error = EINVAL; 166 goto out; 167 } 168 169 if (dp->d_fileno == fileno) { 170 char *bp = *bpp; 171 172 if (offsetof(struct dirent, d_name) + 173 dp->d_namlen > reclen) { 174 error = EINVAL; 175 goto out; 176 } 177 bp -= dp->d_namlen; 178 if (bp <= bufp) { 179 error = ERANGE; 180 goto out; 181 } 182 183 memmove(bp, dp->d_name, dp->d_namlen); 184 error = 0; 185 *bpp = bp; 186 187 goto out; 188 } 189 190 cpos += reclen; 191 } 192 193 } while (!eofflag); 194 195 error = ENOENT; 196 197 out: 198 199 vrele(lvp); 200 *lvpp = NULL; 201 202 free(dirbuf, M_TEMP, dirbuflen); 203 204 return (error); 205 } 206 207 /* Do a lookup in the vnode-to-name reverse */ 208 int 209 vfs_getcwd_getcache(struct vnode **lvpp, struct vnode **uvpp, char **bpp, 210 char *bufp) 211 { 212 struct vnode *lvp, *uvp = NULL; 213 char *obp; 214 int error, vpid; 215 216 lvp = *lvpp; 217 obp = *bpp; /* Save original position to restore to on error */ 218 219 error = cache_revlookup(lvp, uvpp, bpp, bufp); 220 if (error) { 221 if (error != -1) { 222 vput(lvp); 223 *lvpp = NULL; 224 *uvpp = NULL; 225 } 226 227 return (error); 228 } 229 230 uvp = *uvpp; 231 vpid = uvp->v_id; 232 233 234 /* Release current lock before acquiring the parent lock */ 235 VOP_UNLOCK(lvp); 236 237 error = vget(uvp, LK_EXCLUSIVE | LK_RETRY); 238 if (error) 239 *uvpp = NULL; 240 241 /* 242 * Verify that vget() succeeded, and check that vnode capability 243 * didn't change while we were waiting for the lock. 244 */ 245 if (error || (vpid != uvp->v_id)) { 246 /* 247 * Try to get our lock back. If that works, tell the caller to 248 * try things the hard way, otherwise give up. 249 */ 250 if (!error) 251 vput(uvp); 252 253 *uvpp = NULL; 254 255 error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY); 256 if (!error) { 257 *bpp = obp; /* restore the buffer */ 258 return (-1); 259 } 260 } 261 262 vrele(lvp); 263 *lvpp = NULL; 264 265 return (error); 266 } 267 268 /* Common routine shared by sys___getcwd() and vn_isunder() and sys___realpath() */ 269 int 270 vfs_getcwd_common(struct vnode *lvp, struct vnode *rvp, char **bpp, char *bufp, 271 int limit, int flags, struct proc *p) 272 { 273 struct filedesc *fdp = p->p_fd; 274 struct vnode *uvp = NULL; 275 char *bp = NULL; 276 int error, perms = VEXEC; 277 278 if (rvp == NULL) { 279 rvp = fdp->fd_rdir; 280 if (rvp == NULL) 281 rvp = rootvnode; 282 } 283 284 vref(rvp); 285 vref(lvp); 286 287 error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY); 288 if (error) { 289 vrele(lvp); 290 lvp = NULL; 291 goto out; 292 } 293 294 if (bufp) 295 bp = *bpp; 296 297 if (lvp == rvp) { 298 if (bp) 299 *(--bp) = '/'; 300 goto out; 301 } 302 303 /* 304 * This loop will terminate when we hit the root, VOP_READDIR() or 305 * VOP_LOOKUP() fails, or we run out of space in the user buffer. 306 */ 307 do { 308 if (lvp->v_type != VDIR) { 309 error = ENOTDIR; 310 goto out; 311 } 312 313 /* Check for access if caller cares */ 314 if (flags & GETCWD_CHECK_ACCESS) { 315 error = VOP_ACCESS(lvp, perms, p->p_ucred, p); 316 if (error) 317 goto out; 318 perms = VEXEC|VREAD; 319 } 320 321 /* Step up if we're a covered vnode */ 322 while (lvp->v_flag & VROOT) { 323 struct vnode *tvp; 324 325 if (lvp == rvp) 326 goto out; 327 328 tvp = lvp; 329 lvp = lvp->v_mount->mnt_vnodecovered; 330 331 vput(tvp); 332 333 if (lvp == NULL) { 334 error = ENOENT; 335 goto out; 336 } 337 338 vref(lvp); 339 340 error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY); 341 if (error) { 342 vrele(lvp); 343 lvp = NULL; 344 goto out; 345 } 346 } 347 348 /* Look in the name cache */ 349 error = vfs_getcwd_getcache(&lvp, &uvp, &bp, bufp); 350 351 if (error == -1) { 352 /* If that fails, look in the directory */ 353 error = vfs_getcwd_scandir(&lvp, &uvp, &bp, bufp, p); 354 } 355 356 if (error) 357 goto out; 358 359 #ifdef DIAGNOSTIC 360 if (lvp != NULL) 361 panic("getcwd: oops, forgot to null lvp"); 362 if (bufp && (bp <= bufp)) { 363 panic("getcwd: oops, went back too far"); 364 } 365 #endif 366 367 if (bp) 368 *(--bp) = '/'; 369 370 lvp = uvp; 371 uvp = NULL; 372 limit--; 373 374 } while ((lvp != rvp) && (limit > 0)); 375 376 out: 377 378 if (bpp) 379 *bpp = bp; 380 381 if (uvp) 382 vput(uvp); 383 384 if (lvp) 385 vput(lvp); 386 387 vrele(rvp); 388 389 return (error); 390 } 391 392 /* Find pathname of a process's current directory */ 393 int 394 sys___getcwd(struct proc *p, void *v, register_t *retval) 395 { 396 struct sys___getcwd_args *uap = v; 397 int error, len = SCARG(uap, len); 398 char *path, *bp; 399 400 if (len > MAXPATHLEN * 4) 401 len = MAXPATHLEN * 4; 402 else if (len < 2) 403 return (ERANGE); 404 405 path = malloc(len, M_TEMP, M_WAITOK); 406 407 bp = &path[len - 1]; 408 *bp = '\0'; 409 410 /* 411 * 5th argument here is "max number of vnodes to traverse". 412 * Since each entry takes up at least 2 bytes in the output 413 * buffer, limit it to N/2 vnodes for an N byte buffer. 414 */ 415 error = vfs_getcwd_common(p->p_fd->fd_cdir, NULL, &bp, path, len/2, 416 GETCWD_CHECK_ACCESS, p); 417 418 if (error) 419 goto out; 420 421 /* Put the result into user buffer */ 422 error = copyoutstr(bp, SCARG(uap, buf), MAXPATHLEN, NULL); 423 424 #ifdef KTRACE 425 if (KTRPOINT(p, KTR_NAMEI)) 426 ktrnamei(p, bp); 427 #endif 428 429 out: 430 free(path, M_TEMP, len); 431 432 return (error); 433 } 434