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