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