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