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 <machine/../linux/linux.h> 63 #include <machine/../linux/linux_proto.h> 64 #include <compat/linux/linux_util.h> 65 66 static int 67 linux_getcwd_scandir(struct vnode **, struct vnode **, 68 char **, char *, struct thread *); 69 static int 70 linux_getcwd_common(struct vnode *, struct vnode *, 71 char **, char *, int, int, struct thread *); 72 73 #define DIRENT_MINSIZE (sizeof(struct dirent) - (MAXNAMLEN+1) + 4) 74 75 /* 76 * Vnode variable naming conventions in this file: 77 * 78 * rvp: the current root we're aiming towards. 79 * lvp, *lvpp: the "lower" vnode 80 * uvp, *uvpp: the "upper" vnode. 81 * 82 * Since all the vnodes we're dealing with are directories, and the 83 * lookups are going *up* in the filesystem rather than *down*, the 84 * usual "pvp" (parent) or "dvp" (directory) naming conventions are 85 * too confusing. 86 */ 87 88 /* 89 * XXX Will infinite loop in certain cases if a directory read reliably 90 * returns EINVAL on last block. 91 * XXX is EINVAL the right thing to return if a directory is malformed? 92 */ 93 94 /* 95 * XXX Untested vs. mount -o union; probably does the wrong thing. 96 */ 97 98 /* 99 * Find parent vnode of *lvpp, return in *uvpp 100 * 101 * If we care about the name, scan it looking for name of directory 102 * entry pointing at lvp. 103 * 104 * Place the name in the buffer which starts at bufp, immediately 105 * before *bpp, and move bpp backwards to point at the start of it. 106 * 107 * On entry, *lvpp is a locked vnode reference; on exit, it is vput and NULL'ed 108 * On exit, *uvpp is either NULL or is a locked vnode reference. 109 */ 110 static int 111 linux_getcwd_scandir(lvpp, uvpp, bpp, bufp, td) 112 struct vnode **lvpp; 113 struct vnode **uvpp; 114 char **bpp; 115 char *bufp; 116 struct thread *td; 117 { 118 int error = 0; 119 int eofflag; 120 off_t off; 121 int tries; 122 struct uio uio; 123 struct iovec iov; 124 char *dirbuf = NULL; 125 int dirbuflen; 126 ino_t fileno; 127 struct vattr va; 128 struct vnode *uvp = NULL; 129 struct vnode *lvp = *lvpp; 130 struct componentname cn; 131 int len, reclen; 132 tries = 0; 133 134 /* 135 * If we want the filename, get some info we need while the 136 * current directory is still locked. 137 */ 138 if (bufp != NULL) { 139 error = VOP_GETATTR(lvp, &va, td->td_ucred, td); 140 if (error) { 141 vput(lvp); 142 *lvpp = NULL; 143 *uvpp = NULL; 144 return error; 145 } 146 } 147 148 /* 149 * Ok, we have to do it the hard way.. 150 * Next, get parent vnode using lookup of .. 151 */ 152 cn.cn_nameiop = LOOKUP; 153 cn.cn_flags = ISLASTCN | ISDOTDOT | RDONLY; 154 cn.cn_thread = td; 155 cn.cn_cred = td->td_ucred; 156 cn.cn_pnbuf = NULL; 157 cn.cn_nameptr = ".."; 158 cn.cn_namelen = 2; 159 cn.cn_consume = 0; 160 161 /* 162 * At this point, lvp is locked and will be unlocked by the lookup. 163 * On successful return, *uvpp will be locked 164 */ 165 error = VOP_LOOKUP(lvp, uvpp, &cn); 166 if (error) { 167 vput(lvp); 168 *lvpp = NULL; 169 *uvpp = NULL; 170 return error; 171 } 172 uvp = *uvpp; 173 174 /* If we don't care about the pathname, we're done */ 175 if (bufp == NULL) { 176 vrele(lvp); 177 *lvpp = NULL; 178 return 0; 179 } 180 181 fileno = va.va_fileid; 182 183 dirbuflen = DIRBLKSIZ; 184 if (dirbuflen < va.va_blocksize) 185 dirbuflen = va.va_blocksize; 186 dirbuf = (char *)malloc(dirbuflen, M_TEMP, M_WAITOK); 187 188 #if 0 189 unionread: 190 #endif 191 off = 0; 192 do { 193 /* call VOP_READDIR of parent */ 194 iov.iov_base = dirbuf; 195 iov.iov_len = dirbuflen; 196 197 uio.uio_iov = &iov; 198 uio.uio_iovcnt = 1; 199 uio.uio_offset = off; 200 uio.uio_resid = dirbuflen; 201 uio.uio_segflg = UIO_SYSSPACE; 202 uio.uio_rw = UIO_READ; 203 uio.uio_td = td; 204 205 eofflag = 0; 206 207 #ifdef MAC 208 error = mac_check_vnode_readdir(td->td_ucred, uvp); 209 if (error == 0) 210 #endif /* MAC */ 211 error = VOP_READDIR(uvp, &uio, td->td_ucred, &eofflag, 212 0, 0); 213 214 off = uio.uio_offset; 215 216 /* 217 * Try again if NFS tosses its cookies. 218 * XXX this can still loop forever if the directory is busted 219 * such that the second or subsequent page of it always 220 * returns EINVAL 221 */ 222 if ((error == EINVAL) && (tries < 3)) { 223 off = 0; 224 tries++; 225 continue; /* once more, with feeling */ 226 } 227 228 if (!error) { 229 char *cpos; 230 struct dirent *dp; 231 232 cpos = dirbuf; 233 tries = 0; 234 235 /* scan directory page looking for matching vnode */ 236 for (len = (dirbuflen - uio.uio_resid); len > 0; len -= reclen) { 237 dp = (struct dirent *) cpos; 238 reclen = dp->d_reclen; 239 240 /* check for malformed directory.. */ 241 if (reclen < DIRENT_MINSIZE) { 242 error = EINVAL; 243 goto out; 244 } 245 /* 246 * XXX should perhaps do VOP_LOOKUP to 247 * check that we got back to the right place, 248 * but getting the locking games for that 249 * right would be heinous. 250 */ 251 if ((dp->d_type != DT_WHT) && 252 (dp->d_fileno == fileno)) { 253 char *bp = *bpp; 254 bp -= dp->d_namlen; 255 256 if (bp <= bufp) { 257 error = ERANGE; 258 goto out; 259 } 260 bcopy(dp->d_name, bp, dp->d_namlen); 261 error = 0; 262 *bpp = bp; 263 goto out; 264 } 265 cpos += reclen; 266 } 267 } 268 } while (!eofflag); 269 error = ENOENT; 270 271 out: 272 vrele(lvp); 273 *lvpp = NULL; 274 free(dirbuf, M_TEMP); 275 return error; 276 } 277 278 279 /* 280 * common routine shared by sys___getcwd() and linux_vn_isunder() 281 */ 282 283 #define GETCWD_CHECK_ACCESS 0x0001 284 285 static int 286 linux_getcwd_common (lvp, rvp, bpp, bufp, limit, flags, td) 287 struct vnode *lvp; 288 struct vnode *rvp; 289 char **bpp; 290 char *bufp; 291 int limit; 292 int flags; 293 struct thread *td; 294 { 295 struct filedesc *fdp = td->td_proc->p_fd; 296 struct vnode *uvp = NULL; 297 char *bp = NULL; 298 int error; 299 int perms = VEXEC; 300 301 if (rvp == NULL) { 302 rvp = fdp->fd_rdir; 303 if (rvp == NULL) 304 rvp = rootvnode; 305 } 306 307 VREF(rvp); 308 VREF(lvp); 309 310 /* 311 * Error handling invariant: 312 * Before a `goto out': 313 * lvp is either NULL, or locked and held. 314 * uvp is either NULL, or locked and held. 315 */ 316 317 error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY, td); 318 if (error) { 319 vrele(lvp); 320 lvp = NULL; 321 goto out; 322 } 323 if (bufp) 324 bp = *bpp; 325 /* 326 * this loop will terminate when one of the following happens: 327 * - we hit the root 328 * - getdirentries or lookup fails 329 * - we run out of space in the buffer. 330 */ 331 if (lvp == rvp) { 332 if (bp) 333 *(--bp) = '/'; 334 goto out; 335 } 336 do { 337 if (lvp->v_type != VDIR) { 338 error = ENOTDIR; 339 goto out; 340 } 341 342 /* 343 * access check here is optional, depending on 344 * whether or not caller cares. 345 */ 346 if (flags & GETCWD_CHECK_ACCESS) { 347 error = VOP_ACCESS(lvp, perms, td->td_ucred, td); 348 if (error) 349 goto out; 350 perms = VEXEC|VREAD; 351 } 352 353 /* 354 * step up if we're a covered vnode.. 355 */ 356 while (lvp->v_vflag & VV_ROOT) { 357 struct vnode *tvp; 358 359 if (lvp == rvp) 360 goto out; 361 362 tvp = lvp; 363 lvp = lvp->v_mount->mnt_vnodecovered; 364 vput(tvp); 365 /* 366 * hodie natus est radici frater 367 */ 368 if (lvp == NULL) { 369 error = ENOENT; 370 goto out; 371 } 372 VREF(lvp); 373 error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY, td); 374 if (error != 0) { 375 vrele(lvp); 376 lvp = NULL; 377 goto out; 378 } 379 } 380 error = linux_getcwd_scandir(&lvp, &uvp, &bp, bufp, td); 381 if (error) 382 goto out; 383 #if DIAGNOSTIC 384 if (lvp != NULL) 385 panic("getcwd: oops, forgot to null lvp"); 386 if (bufp && (bp <= bufp)) { 387 panic("getcwd: oops, went back too far"); 388 } 389 #endif 390 if (bp) 391 *(--bp) = '/'; 392 lvp = uvp; 393 uvp = NULL; 394 limit--; 395 } while ((lvp != rvp) && (limit > 0)); 396 397 out: 398 if (bpp) 399 *bpp = bp; 400 if (uvp) 401 vput(uvp); 402 if (lvp) 403 vput(lvp); 404 vrele(rvp); 405 return error; 406 } 407 408 409 /* 410 * Find pathname of process's current directory. 411 * 412 * Use vfs vnode-to-name reverse cache; if that fails, fall back 413 * to reading directory contents. 414 */ 415 416 int 417 linux_getcwd(struct thread *td, struct linux_getcwd_args *args) 418 { 419 caddr_t bp, bend, path; 420 int error, len, lenused; 421 422 #ifdef DEBUG 423 printf("Linux-emul(%ld): getcwd(%p, %ld)\n", (long)td->td_proc->p_pid, 424 args->buf, (long)args->bufsize); 425 #endif 426 427 len = args->bufsize; 428 429 if (len > MAXPATHLEN*4) 430 len = MAXPATHLEN*4; 431 else if (len < 2) 432 return ERANGE; 433 434 path = (char *)malloc(len, M_TEMP, M_WAITOK); 435 436 error = kern___getcwd(td, path, UIO_SYSSPACE, len); 437 if (!error) { 438 lenused = strlen(path) + 1; 439 if (lenused <= args->bufsize) { 440 td->td_retval[0] = lenused; 441 error = copyout(path, args->buf, lenused); 442 } 443 else 444 error = ERANGE; 445 } else { 446 bp = &path[len]; 447 bend = bp; 448 *(--bp) = '\0'; 449 450 /* 451 * 5th argument here is "max number of vnodes to traverse". 452 * Since each entry takes up at least 2 bytes in the output buffer, 453 * limit it to N/2 vnodes for an N byte buffer. 454 */ 455 456 error = linux_getcwd_common (td->td_proc->p_fd->fd_cdir, NULL, 457 &bp, path, len/2, GETCWD_CHECK_ACCESS, td); 458 459 if (error) 460 goto out; 461 lenused = bend - bp; 462 td->td_retval[0] = lenused; 463 /* put the result into user buffer */ 464 error = copyout(bp, args->buf, lenused); 465 } 466 out: 467 free(path, M_TEMP); 468 return (error); 469 } 470 471