1 /* $OpenBSD: linux_getcwd.c,v 1.2 2001/05/16 12:50:21 ho Exp $ */ 2 /* $NetBSD: vfs_getcwd.c,v 1.3.2.3 1999/07/11 10:24:09 sommerfeld Exp $ */ 3 /*- 4 * Copyright (c) 1999 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Bill Sommerfeld. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 3. All advertising materials mentioning features or use of this software 19 * must display the following acknowledgement: 20 * This product includes software developed by the NetBSD 21 * Foundation, Inc. and its contributors. 22 * 4. Neither the name of The NetBSD Foundation nor the names of its 23 * contributors may be used to endorse or promote products derived 24 * from this software without specific prior written permission. 25 * 26 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 27 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 28 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 29 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 30 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 32 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 33 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 36 * POSSIBILITY OF SUCH DAMAGE. 37 */ 38 39 #include <sys/cdefs.h> 40 __FBSDID("$FreeBSD$"); 41 42 #include "opt_compat.h" 43 #include "opt_mac.h" 44 45 #include <sys/param.h> 46 #include <sys/systm.h> 47 #include <sys/namei.h> 48 #include <sys/filedesc.h> 49 #include <sys/kernel.h> 50 #include <sys/file.h> 51 #include <sys/stat.h> 52 #include <sys/syscallsubr.h> 53 #include <sys/vnode.h> 54 #include <sys/mount.h> 55 #include <sys/proc.h> 56 #include <sys/uio.h> 57 #include <sys/mac.h> 58 #include <sys/malloc.h> 59 #include <sys/dirent.h> 60 #include <ufs/ufs/dir.h> /* XXX only for DIRBLKSIZ */ 61 62 #include "opt_compat.h" 63 64 #ifdef COMPAT_LINUX32 65 #include <machine/../linux32/linux.h> 66 #include <machine/../linux32/linux32_proto.h> 67 #else 68 #include <machine/../linux/linux.h> 69 #include <machine/../linux/linux_proto.h> 70 #endif 71 #include <compat/linux/linux_util.h> 72 73 static int 74 linux_getcwd_scandir(struct vnode **, struct vnode **, 75 char **, char *, struct thread *); 76 static int 77 linux_getcwd_common(struct vnode *, struct vnode *, 78 char **, char *, int, int, struct thread *); 79 80 #define DIRENT_MINSIZE (sizeof(struct dirent) - (MAXNAMLEN+1) + 4) 81 82 /* 83 * Vnode variable naming conventions in this file: 84 * 85 * rvp: the current root we're aiming towards. 86 * lvp, *lvpp: the "lower" vnode 87 * uvp, *uvpp: the "upper" vnode. 88 * 89 * Since all the vnodes we're dealing with are directories, and the 90 * lookups are going *up* in the filesystem rather than *down*, the 91 * usual "pvp" (parent) or "dvp" (directory) naming conventions are 92 * too confusing. 93 */ 94 95 /* 96 * XXX Will infinite loop in certain cases if a directory read reliably 97 * returns EINVAL on last block. 98 * XXX is EINVAL the right thing to return if a directory is malformed? 99 */ 100 101 /* 102 * XXX Untested vs. mount -o union; probably does the wrong thing. 103 */ 104 105 /* 106 * Find parent vnode of *lvpp, return in *uvpp 107 * 108 * If we care about the name, scan it looking for name of directory 109 * entry pointing at lvp. 110 * 111 * Place the name in the buffer which starts at bufp, immediately 112 * before *bpp, and move bpp backwards to point at the start of it. 113 * 114 * On entry, *lvpp is a locked vnode reference; on exit, it is vput and NULL'ed 115 * On exit, *uvpp is either NULL or is a locked vnode reference. 116 */ 117 static int 118 linux_getcwd_scandir(lvpp, uvpp, bpp, bufp, td) 119 struct vnode **lvpp; 120 struct vnode **uvpp; 121 char **bpp; 122 char *bufp; 123 struct thread *td; 124 { 125 int error = 0; 126 int eofflag; 127 off_t off; 128 int tries; 129 struct uio uio; 130 struct iovec iov; 131 char *dirbuf = NULL; 132 int dirbuflen; 133 ino_t fileno; 134 struct vattr va; 135 struct vnode *uvp = NULL; 136 struct vnode *lvp = *lvpp; 137 struct componentname cn; 138 int len, reclen; 139 tries = 0; 140 141 /* 142 * If we want the filename, get some info we need while the 143 * current directory is still locked. 144 */ 145 if (bufp != NULL) { 146 error = VOP_GETATTR(lvp, &va, td->td_ucred, td); 147 if (error) { 148 vput(lvp); 149 *lvpp = NULL; 150 *uvpp = NULL; 151 return error; 152 } 153 } 154 155 /* 156 * Ok, we have to do it the hard way.. 157 * Next, get parent vnode using lookup of .. 158 */ 159 cn.cn_nameiop = LOOKUP; 160 cn.cn_flags = ISLASTCN | ISDOTDOT | RDONLY; 161 cn.cn_thread = td; 162 cn.cn_cred = td->td_ucred; 163 cn.cn_pnbuf = NULL; 164 cn.cn_nameptr = ".."; 165 cn.cn_namelen = 2; 166 cn.cn_consume = 0; 167 cn.cn_lkflags = LK_EXCLUSIVE; 168 169 /* 170 * At this point, lvp is locked and will be unlocked by the lookup. 171 * On successful return, *uvpp will be locked 172 */ 173 #ifdef MAC 174 error = mac_check_vnode_lookup(td->td_ucred, lvp, &cn); 175 if (error == 0) 176 #endif 177 error = VOP_LOOKUP(lvp, uvpp, &cn); 178 if (error) { 179 vput(lvp); 180 *lvpp = NULL; 181 *uvpp = NULL; 182 return error; 183 } 184 uvp = *uvpp; 185 186 /* If we don't care about the pathname, we're done */ 187 if (bufp == NULL) { 188 vput(lvp); 189 *lvpp = NULL; 190 return 0; 191 } 192 193 fileno = va.va_fileid; 194 195 dirbuflen = DIRBLKSIZ; 196 if (dirbuflen < va.va_blocksize) 197 dirbuflen = va.va_blocksize; 198 dirbuf = (char *)malloc(dirbuflen, M_TEMP, M_WAITOK); 199 200 #if 0 201 unionread: 202 #endif 203 off = 0; 204 do { 205 /* call VOP_READDIR of parent */ 206 iov.iov_base = dirbuf; 207 iov.iov_len = dirbuflen; 208 209 uio.uio_iov = &iov; 210 uio.uio_iovcnt = 1; 211 uio.uio_offset = off; 212 uio.uio_resid = dirbuflen; 213 uio.uio_segflg = UIO_SYSSPACE; 214 uio.uio_rw = UIO_READ; 215 uio.uio_td = td; 216 217 eofflag = 0; 218 219 #ifdef MAC 220 error = mac_check_vnode_readdir(td->td_ucred, uvp); 221 if (error == 0) 222 #endif /* MAC */ 223 error = VOP_READDIR(uvp, &uio, td->td_ucred, &eofflag, 224 0, 0); 225 226 off = uio.uio_offset; 227 228 /* 229 * Try again if NFS tosses its cookies. 230 * XXX this can still loop forever if the directory is busted 231 * such that the second or subsequent page of it always 232 * returns EINVAL 233 */ 234 if ((error == EINVAL) && (tries < 3)) { 235 off = 0; 236 tries++; 237 continue; /* once more, with feeling */ 238 } 239 240 if (!error) { 241 char *cpos; 242 struct dirent *dp; 243 244 cpos = dirbuf; 245 tries = 0; 246 247 /* scan directory page looking for matching vnode */ 248 for (len = (dirbuflen - uio.uio_resid); len > 0; len -= reclen) { 249 dp = (struct dirent *) cpos; 250 reclen = dp->d_reclen; 251 252 /* check for malformed directory.. */ 253 if (reclen < DIRENT_MINSIZE) { 254 error = EINVAL; 255 goto out; 256 } 257 /* 258 * XXX should perhaps do VOP_LOOKUP to 259 * check that we got back to the right place, 260 * but getting the locking games for that 261 * right would be heinous. 262 */ 263 if ((dp->d_type != DT_WHT) && 264 (dp->d_fileno == fileno)) { 265 char *bp = *bpp; 266 bp -= dp->d_namlen; 267 268 if (bp <= bufp) { 269 error = ERANGE; 270 goto out; 271 } 272 bcopy(dp->d_name, bp, dp->d_namlen); 273 error = 0; 274 *bpp = bp; 275 goto out; 276 } 277 cpos += reclen; 278 } 279 } 280 } while (!eofflag); 281 error = ENOENT; 282 283 out: 284 vput(lvp); 285 *lvpp = NULL; 286 free(dirbuf, M_TEMP); 287 return error; 288 } 289 290 291 /* 292 * common routine shared by sys___getcwd() and linux_vn_isunder() 293 */ 294 295 #define GETCWD_CHECK_ACCESS 0x0001 296 297 static int 298 linux_getcwd_common (lvp, rvp, bpp, bufp, limit, flags, td) 299 struct vnode *lvp; 300 struct vnode *rvp; 301 char **bpp; 302 char *bufp; 303 int limit; 304 int flags; 305 struct thread *td; 306 { 307 struct filedesc *fdp = td->td_proc->p_fd; 308 struct vnode *uvp = NULL; 309 char *bp = NULL; 310 int error; 311 int perms = VEXEC; 312 313 if (rvp == NULL) { 314 rvp = fdp->fd_rdir; 315 if (rvp == NULL) 316 rvp = rootvnode; 317 } 318 319 VREF(rvp); 320 VREF(lvp); 321 322 /* 323 * Error handling invariant: 324 * Before a `goto out': 325 * lvp is either NULL, or locked and held. 326 * uvp is either NULL, or locked and held. 327 */ 328 329 error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY, td); 330 if (error) { 331 vrele(lvp); 332 lvp = NULL; 333 goto out; 334 } 335 if (bufp) 336 bp = *bpp; 337 /* 338 * this loop will terminate when one of the following happens: 339 * - we hit the root 340 * - getdirentries or lookup fails 341 * - we run out of space in the buffer. 342 */ 343 if (lvp == rvp) { 344 if (bp) 345 *(--bp) = '/'; 346 goto out; 347 } 348 do { 349 if (lvp->v_type != VDIR) { 350 error = ENOTDIR; 351 goto out; 352 } 353 354 /* 355 * access check here is optional, depending on 356 * whether or not caller cares. 357 */ 358 if (flags & GETCWD_CHECK_ACCESS) { 359 error = VOP_ACCESS(lvp, perms, td->td_ucred, td); 360 if (error) 361 goto out; 362 perms = VEXEC|VREAD; 363 } 364 365 /* 366 * step up if we're a covered vnode.. 367 */ 368 while (lvp->v_vflag & VV_ROOT) { 369 struct vnode *tvp; 370 371 if (lvp == rvp) 372 goto out; 373 374 tvp = lvp; 375 lvp = lvp->v_mount->mnt_vnodecovered; 376 vput(tvp); 377 /* 378 * hodie natus est radici frater 379 */ 380 if (lvp == NULL) { 381 error = ENOENT; 382 goto out; 383 } 384 VREF(lvp); 385 error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY, td); 386 if (error != 0) { 387 vrele(lvp); 388 lvp = NULL; 389 goto out; 390 } 391 } 392 error = linux_getcwd_scandir(&lvp, &uvp, &bp, bufp, td); 393 if (error) 394 goto out; 395 #ifdef DIAGNOSTIC 396 if (lvp != NULL) 397 panic("getcwd: oops, forgot to null lvp"); 398 if (bufp && (bp <= bufp)) { 399 panic("getcwd: oops, went back too far"); 400 } 401 #endif 402 if (bp) 403 *(--bp) = '/'; 404 lvp = uvp; 405 uvp = NULL; 406 limit--; 407 } while ((lvp != rvp) && (limit > 0)); 408 409 out: 410 if (bpp) 411 *bpp = bp; 412 if (uvp) 413 vput(uvp); 414 if (lvp) 415 vput(lvp); 416 vrele(rvp); 417 return error; 418 } 419 420 421 /* 422 * Find pathname of process's current directory. 423 * 424 * Use vfs vnode-to-name reverse cache; if that fails, fall back 425 * to reading directory contents. 426 */ 427 428 int 429 linux_getcwd(struct thread *td, struct linux_getcwd_args *args) 430 { 431 caddr_t bp, bend, path; 432 int error, len, lenused; 433 434 #ifdef DEBUG 435 printf("Linux-emul(%ld): getcwd(%p, %ld)\n", (long)td->td_proc->p_pid, 436 args->buf, (long)args->bufsize); 437 #endif 438 439 len = args->bufsize; 440 441 if (len > MAXPATHLEN*4) 442 len = MAXPATHLEN*4; 443 else if (len < 2) 444 return ERANGE; 445 446 path = (char *)malloc(len, M_TEMP, M_WAITOK); 447 448 error = kern___getcwd(td, path, UIO_SYSSPACE, len); 449 if (!error) { 450 lenused = strlen(path) + 1; 451 if (lenused <= args->bufsize) { 452 td->td_retval[0] = lenused; 453 error = copyout(path, args->buf, lenused); 454 } 455 else 456 error = ERANGE; 457 } else { 458 bp = &path[len]; 459 bend = bp; 460 *(--bp) = '\0'; 461 462 /* 463 * 5th argument here is "max number of vnodes to traverse". 464 * Since each entry takes up at least 2 bytes in the output buffer, 465 * limit it to N/2 vnodes for an N byte buffer. 466 */ 467 468 mtx_lock(&Giant); 469 error = linux_getcwd_common (td->td_proc->p_fd->fd_cdir, NULL, 470 &bp, path, len/2, GETCWD_CHECK_ACCESS, td); 471 mtx_unlock(&Giant); 472 473 if (error) 474 goto out; 475 lenused = bend - bp; 476 td->td_retval[0] = lenused; 477 /* put the result into user buffer */ 478 error = copyout(bp, args->buf, lenused); 479 } 480 out: 481 free(path, M_TEMP); 482 return (error); 483 } 484 485