1 /* $OpenBSD: vfs_getcwd.c,v 1.26 2016/03/19 12:04:15 natano 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/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, 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 vrele(lvp); 106 *lvpp = NULL; 107 return (0); 108 } 109 110 fileno = va.va_fileid; 111 112 dirbuflen = DIRBLKSIZ; 113 114 if (dirbuflen < va.va_blocksize) 115 dirbuflen = va.va_blocksize; 116 117 dirbuf = malloc(dirbuflen, M_TEMP, M_WAITOK); 118 119 off = 0; 120 121 do { 122 char *cpos; 123 struct dirent *dp; 124 125 iov.iov_base = dirbuf; 126 iov.iov_len = dirbuflen; 127 128 uio.uio_iov = &iov; 129 uio.uio_iovcnt = 1; 130 uio.uio_offset = off; 131 uio.uio_resid = dirbuflen; 132 uio.uio_segflg = UIO_SYSSPACE; 133 uio.uio_rw = UIO_READ; 134 uio.uio_procp = p; 135 136 eofflag = 0; 137 138 /* Call VOP_READDIR of parent */ 139 error = VOP_READDIR(uvp, &uio, p->p_ucred, &eofflag); 140 141 off = uio.uio_offset; 142 143 /* Try again if NFS tosses its cookies */ 144 if (error == EINVAL && tries < 3) { 145 tries++; 146 off = 0; 147 continue; 148 } else if (error) { 149 goto out; /* Old userland getcwd() behaviour */ 150 } 151 152 cpos = dirbuf; 153 tries = 0; 154 155 /* Scan directory page looking for matching vnode */ 156 for (len = (dirbuflen - uio.uio_resid); len > 0; 157 len -= reclen) { 158 dp = (struct dirent *)cpos; 159 reclen = dp->d_reclen; 160 161 /* Check for malformed directory */ 162 if (reclen < DIRENT_RECSIZE(1)) { 163 error = EINVAL; 164 goto out; 165 } 166 167 if (dp->d_fileno == fileno) { 168 char *bp = *bpp; 169 bp -= dp->d_namlen; 170 171 if (bp <= bufp) { 172 error = ERANGE; 173 goto out; 174 } 175 176 memmove(bp, dp->d_name, dp->d_namlen); 177 error = 0; 178 *bpp = bp; 179 180 goto out; 181 } 182 183 cpos += reclen; 184 } 185 186 } while (!eofflag); 187 188 error = ENOENT; 189 190 out: 191 192 vrele(lvp); 193 *lvpp = NULL; 194 195 free(dirbuf, M_TEMP, dirbuflen); 196 197 return (error); 198 } 199 200 /* Do a lookup in the vnode-to-name reverse */ 201 int 202 vfs_getcwd_getcache(struct vnode **lvpp, struct vnode **uvpp, char **bpp, 203 char *bufp) 204 { 205 struct vnode *lvp, *uvp = NULL; 206 struct proc *p = curproc; 207 char *obp; 208 int error, vpid; 209 210 lvp = *lvpp; 211 obp = *bpp; /* Save original position to restore to on error */ 212 213 error = cache_revlookup(lvp, uvpp, bpp, bufp); 214 if (error) { 215 if (error != -1) { 216 vput(lvp); 217 *lvpp = NULL; 218 *uvpp = NULL; 219 } 220 221 return (error); 222 } 223 224 uvp = *uvpp; 225 vpid = uvp->v_id; 226 227 228 /* Release current lock before acquiring the parent lock */ 229 VOP_UNLOCK(lvp, p); 230 231 error = vget(uvp, LK_EXCLUSIVE | LK_RETRY, p); 232 if (error) 233 *uvpp = NULL; 234 235 /* 236 * Verify that vget() succeeded, and check that vnode capability 237 * didn't change while we were waiting for the lock. 238 */ 239 if (error || (vpid != uvp->v_id)) { 240 /* 241 * Try to get our lock back. If that works, tell the caller to 242 * try things the hard way, otherwise give up. 243 */ 244 if (!error) 245 vput(uvp); 246 247 *uvpp = NULL; 248 249 error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY, p); 250 if (!error) { 251 *bpp = obp; /* restore the buffer */ 252 return (-1); 253 } 254 } 255 256 vrele(lvp); 257 *lvpp = NULL; 258 259 return (error); 260 } 261 262 /* Common routine shared by sys___getcwd() and vn_isunder() */ 263 int 264 vfs_getcwd_common(struct vnode *lvp, struct vnode *rvp, char **bpp, char *bufp, 265 int limit, int flags, struct proc *p) 266 { 267 struct filedesc *fdp = p->p_fd; 268 struct vnode *uvp = NULL; 269 char *bp = NULL; 270 int error, perms = VEXEC; 271 272 if (rvp == NULL) { 273 rvp = fdp->fd_rdir; 274 if (rvp == NULL) 275 rvp = rootvnode; 276 } 277 278 vref(rvp); 279 vref(lvp); 280 281 error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY, p); 282 if (error) { 283 vrele(lvp); 284 lvp = NULL; 285 goto out; 286 } 287 288 if (bufp) 289 bp = *bpp; 290 291 if (lvp == rvp) { 292 if (bp) 293 *(--bp) = '/'; 294 goto out; 295 } 296 297 /* 298 * This loop will terminate when we hit the root, VOP_READDIR() or 299 * VOP_LOOKUP() fails, or we run out of space in the user buffer. 300 */ 301 do { 302 if (lvp->v_type != VDIR) { 303 error = ENOTDIR; 304 goto out; 305 } 306 307 /* Check for access if caller cares */ 308 if (flags & GETCWD_CHECK_ACCESS) { 309 error = VOP_ACCESS(lvp, perms, p->p_ucred, p); 310 if (error) 311 goto out; 312 perms = VEXEC|VREAD; 313 } 314 315 /* Step up if we're a covered vnode */ 316 while (lvp->v_flag & VROOT) { 317 struct vnode *tvp; 318 319 if (lvp == rvp) 320 goto out; 321 322 tvp = lvp; 323 lvp = lvp->v_mount->mnt_vnodecovered; 324 325 vput(tvp); 326 327 if (lvp == NULL) { 328 error = ENOENT; 329 goto out; 330 } 331 332 vref(lvp); 333 334 error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY, p); 335 if (error) { 336 vrele(lvp); 337 lvp = NULL; 338 goto out; 339 } 340 } 341 342 /* Look in the name cache */ 343 error = vfs_getcwd_getcache(&lvp, &uvp, &bp, bufp); 344 345 if (error == -1) { 346 /* If that fails, look in the directory */ 347 error = vfs_getcwd_scandir(&lvp, &uvp, &bp, bufp, p); 348 } 349 350 if (error) 351 goto out; 352 353 #ifdef DIAGNOSTIC 354 if (lvp != NULL) 355 panic("getcwd: oops, forgot to null lvp"); 356 if (bufp && (bp <= bufp)) { 357 panic("getcwd: oops, went back too far"); 358 } 359 #endif 360 361 if (bp) 362 *(--bp) = '/'; 363 364 lvp = uvp; 365 uvp = NULL; 366 limit--; 367 368 } while ((lvp != rvp) && (limit > 0)); 369 370 out: 371 372 if (bpp) 373 *bpp = bp; 374 375 if (uvp) 376 vput(uvp); 377 378 if (lvp) 379 vput(lvp); 380 381 vrele(rvp); 382 383 return (error); 384 } 385 386 /* Find pathname of a process's current directory */ 387 int 388 sys___getcwd(struct proc *p, void *v, register_t *retval) 389 { 390 struct sys___getcwd_args *uap = v; 391 int error, lenused, len = SCARG(uap, len); 392 char *path, *bp, *bend; 393 394 if (len > MAXPATHLEN * 4) 395 len = MAXPATHLEN * 4; 396 else if (len < 2) 397 return (ERANGE); 398 399 path = malloc(len, M_TEMP, M_WAITOK); 400 401 bp = &path[len]; 402 bend = bp; 403 *(--bp) = '\0'; 404 405 /* 406 * 5th argument here is "max number of vnodes to traverse". 407 * Since each entry takes up at least 2 bytes in the output 408 * buffer, limit it to N/2 vnodes for an N byte buffer. 409 */ 410 error = vfs_getcwd_common(p->p_fd->fd_cdir, NULL, &bp, path, len/2, 411 GETCWD_CHECK_ACCESS, p); 412 413 if (error) 414 goto out; 415 416 lenused = bend - bp; 417 *retval = lenused; 418 419 /* Put the result into user buffer */ 420 error = copyout(bp, SCARG(uap, buf), lenused); 421 422 out: 423 free(path, M_TEMP, len); 424 425 return (error); 426 } 427