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