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