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