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