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