From 055d495677c4f59d9e0892b73a96ec32762b9e93 Mon Sep 17 00:00:00 2001 From: Andrew Gallatin Date: Wed, 29 Aug 2001 19:05:27 +0000 Subject: [PATCH] Fix linux_getcwd() so that if the cwd isn't cached (__getcwd() fails), the cwd is looked up inside the kernel. The native getcwd() in libc handles this in userland if __getcwd() fails. Obtained from: NetBSD via OpenBSD Tested by: Chris Casey , Markus Holmberg Reviewed by: Darrell Anderson PR: kern/24315 --- sys/compat/linux/linux_file.c | 28 -- sys/compat/linux/linux_getcwd.c | 466 ++++++++++++++++++++++++++++++++ sys/conf/files.alpha | 1 + sys/conf/files.i386 | 1 + sys/modules/linux/Makefile | 4 +- 5 files changed, 470 insertions(+), 30 deletions(-) create mode 100644 sys/compat/linux/linux_getcwd.c diff --git a/sys/compat/linux/linux_file.c b/sys/compat/linux/linux_file.c index 0f68ab5c6ba..2a5d6732d48 100644 --- a/sys/compat/linux/linux_file.c +++ b/sys/compat/linux/linux_file.c @@ -830,34 +830,6 @@ linux_link(struct proc *p, struct linux_link_args *args) return link(p, &bsd); } -int -linux_getcwd(struct proc *p, struct linux_getcwd_args *args) -{ - struct __getcwd_args bsd; - caddr_t sg; - int error, len; - -#ifdef DEBUG - if (ldebug(getcwd)) - printf(ARGS(getcwd, "%p, %ld"), args->buf, args->bufsize); -#endif - - sg = stackgap_init(); - bsd.buf = stackgap_alloc(&sg, SPARE_USRSPACE); - bsd.buflen = SPARE_USRSPACE; - error = __getcwd(p, &bsd); - if (!error) { - len = strlen(bsd.buf) + 1; - if (len <= args->bufsize) { - p->p_retval[0] = len; - error = copyout(bsd.buf, args->buf, len); - } - else - error = ERANGE; - } - return (error); -} - #ifndef __alpha__ int linux_fdatasync(p, uap) diff --git a/sys/compat/linux/linux_getcwd.c b/sys/compat/linux/linux_getcwd.c new file mode 100644 index 00000000000..2231f8230d5 --- /dev/null +++ b/sys/compat/linux/linux_getcwd.c @@ -0,0 +1,466 @@ +/* $FreeBSD$ */ +/* $OpenBSD: linux_getcwd.c,v 1.2 2001/05/16 12:50:21 ho Exp $ */ +/* $NetBSD: vfs_getcwd.c,v 1.3.2.3 1999/07/11 10:24:09 sommerfeld Exp $ */ + +/*- + * Copyright (c) 1999 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Bill Sommerfeld. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#include "opt_compat.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* XXX only for DIRBLKSIZ */ + +#include +#include +#include + +static int +linux_getcwd_scandir __P((struct vnode **, struct vnode **, + char **, char *, struct proc *)); +static int +linux_getcwd_common __P((struct vnode *, struct vnode *, + char **, char *, int, int, struct proc *)); + +#define DIRENT_MINSIZE (sizeof(struct dirent) - (MAXNAMLEN+1) + 4) + +/* + * Vnode variable naming conventions in this file: + * + * rvp: the current root we're aiming towards. + * lvp, *lvpp: the "lower" vnode + * uvp, *uvpp: the "upper" vnode. + * + * Since all the vnodes we're dealing with are directories, and the + * lookups are going *up* in the filesystem rather than *down*, the + * usual "pvp" (parent) or "dvp" (directory) naming conventions are + * too confusing. + */ + +/* + * XXX Will infinite loop in certain cases if a directory read reliably + * returns EINVAL on last block. + * XXX is EINVAL the right thing to return if a directory is malformed? + */ + +/* + * XXX Untested vs. mount -o union; probably does the wrong thing. + */ + +/* + * Find parent vnode of *lvpp, return in *uvpp + * + * If we care about the name, scan it looking for name of directory + * entry pointing at lvp. + * + * Place the name in the buffer which starts at bufp, immediately + * before *bpp, and move bpp backwards to point at the start of it. + * + * On entry, *lvpp is a locked vnode reference; on exit, it is vput and NULL'ed + * On exit, *uvpp is either NULL or is a locked vnode reference. + */ +static int +linux_getcwd_scandir(lvpp, uvpp, bpp, bufp, p) + struct vnode **lvpp; + struct vnode **uvpp; + char **bpp; + char *bufp; + struct proc *p; +{ + int error = 0; + int eofflag; + off_t off; + int tries; + struct uio uio; + struct iovec iov; + char *dirbuf = NULL; + int dirbuflen; + ino_t fileno; + struct vattr va; + struct vnode *uvp = NULL; + struct vnode *lvp = *lvpp; + struct componentname cn; + int len, reclen; + tries = 0; + + /* + * If we want the filename, get some info we need while the + * current directory is still locked. + */ + if (bufp != NULL) { + error = VOP_GETATTR(lvp, &va, p->p_ucred, p); + if (error) { + vput(lvp); + *lvpp = NULL; + *uvpp = NULL; + return error; + } + } + + /* + * Ok, we have to do it the hard way.. + * Next, get parent vnode using lookup of .. + */ + cn.cn_nameiop = LOOKUP; + cn.cn_flags = ISLASTCN | ISDOTDOT | RDONLY; + cn.cn_proc = p; + cn.cn_cred = p->p_ucred; + cn.cn_pnbuf = NULL; + cn.cn_nameptr = ".."; + cn.cn_namelen = 2; + cn.cn_consume = 0; + + /* + * At this point, lvp is locked and will be unlocked by the lookup. + * On successful return, *uvpp will be locked + */ + error = VOP_LOOKUP(lvp, uvpp, &cn); + if (error) { + vput(lvp); + *lvpp = NULL; + *uvpp = NULL; + return error; + } + uvp = *uvpp; + + /* If we don't care about the pathname, we're done */ + if (bufp == NULL) { + vrele(lvp); + *lvpp = NULL; + return 0; + } + + fileno = va.va_fileid; + + dirbuflen = DIRBLKSIZ; + if (dirbuflen < va.va_blocksize) + dirbuflen = va.va_blocksize; + dirbuf = (char *)malloc(dirbuflen, M_TEMP, M_WAITOK); + +#if 0 +unionread: +#endif + off = 0; + do { + /* call VOP_READDIR of parent */ + iov.iov_base = dirbuf; + iov.iov_len = dirbuflen; + + uio.uio_iov = &iov; + uio.uio_iovcnt = 1; + uio.uio_offset = off; + uio.uio_resid = dirbuflen; + uio.uio_segflg = UIO_SYSSPACE; + uio.uio_rw = UIO_READ; + uio.uio_procp = p; + + eofflag = 0; + + error = VOP_READDIR(uvp, &uio, p->p_ucred, &eofflag, 0, 0); + + off = uio.uio_offset; + + /* + * Try again if NFS tosses its cookies. + * XXX this can still loop forever if the directory is busted + * such that the second or subsequent page of it always + * returns EINVAL + */ + if ((error == EINVAL) && (tries < 3)) { + off = 0; + tries++; + continue; /* once more, with feeling */ + } + + if (!error) { + char *cpos; + struct dirent *dp; + + cpos = dirbuf; + tries = 0; + + /* scan directory page looking for matching vnode */ + for (len = (dirbuflen - uio.uio_resid); len > 0; len -= reclen) { + dp = (struct dirent *) cpos; + reclen = dp->d_reclen; + + /* check for malformed directory.. */ + if (reclen < DIRENT_MINSIZE) { + error = EINVAL; + goto out; + } + /* + * XXX should perhaps do VOP_LOOKUP to + * check that we got back to the right place, + * but getting the locking games for that + * right would be heinous. + */ + if ((dp->d_type != DT_WHT) && + (dp->d_fileno == fileno)) { + char *bp = *bpp; + bp -= dp->d_namlen; + + if (bp <= bufp) { + error = ERANGE; + goto out; + } + bcopy(dp->d_name, bp, dp->d_namlen); + error = 0; + *bpp = bp; + goto out; + } + cpos += reclen; + } + } + } while (!eofflag); + error = ENOENT; + +out: + vrele(lvp); + *lvpp = NULL; + free(dirbuf, M_TEMP); + return error; +} + + +/* + * common routine shared by sys___getcwd() and linux_vn_isunder() + */ + +#define GETCWD_CHECK_ACCESS 0x0001 + +static int +linux_getcwd_common (lvp, rvp, bpp, bufp, limit, flags, p) + struct vnode *lvp; + struct vnode *rvp; + char **bpp; + char *bufp; + int limit; + int flags; + struct proc *p; +{ + struct filedesc *fdp = p->p_fd; + struct vnode *uvp = NULL; + char *bp = NULL; + int error; + int perms = VEXEC; + + if (rvp == NULL) { + rvp = fdp->fd_rdir; + if (rvp == NULL) + rvp = rootvnode; + } + + VREF(rvp); + VREF(lvp); + + /* + * Error handling invariant: + * Before a `goto out': + * lvp is either NULL, or locked and held. + * uvp is either NULL, or locked and held. + */ + + error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY, p); + if (error) { + vrele(lvp); + lvp = NULL; + goto out; + } + if (bufp) + bp = *bpp; + /* + * this loop will terminate when one of the following happens: + * - we hit the root + * - getdirentries or lookup fails + * - we run out of space in the buffer. + */ + if (lvp == rvp) { + if (bp) + *(--bp) = '/'; + goto out; + } + do { + if (lvp->v_type != VDIR) { + error = ENOTDIR; + goto out; + } + + /* + * access check here is optional, depending on + * whether or not caller cares. + */ + if (flags & GETCWD_CHECK_ACCESS) { + error = VOP_ACCESS(lvp, perms, p->p_ucred, p); + if (error) + goto out; + perms = VEXEC|VREAD; + } + + /* + * step up if we're a covered vnode.. + */ + while (lvp->v_flag & VROOT) { + struct vnode *tvp; + + if (lvp == rvp) + goto out; + + tvp = lvp; + lvp = lvp->v_mount->mnt_vnodecovered; + vput(tvp); + /* + * hodie natus est radici frater + */ + if (lvp == NULL) { + error = ENOENT; + goto out; + } + VREF(lvp); + error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY, p); + if (error != 0) { + vrele(lvp); + lvp = NULL; + goto out; + } + } + error = linux_getcwd_scandir(&lvp, &uvp, &bp, bufp, p); + if (error) + goto out; +#if DIAGNOSTIC + if (lvp != NULL) + panic("getcwd: oops, forgot to null lvp"); + if (bufp && (bp <= bufp)) { + panic("getcwd: oops, went back too far"); + } +#endif + if (bp) + *(--bp) = '/'; + lvp = uvp; + uvp = NULL; + limit--; + } while ((lvp != rvp) && (limit > 0)); + +out: + if (bpp) + *bpp = bp; + if (uvp) + vput(uvp); + if (lvp) + vput(lvp); + vrele(rvp); + return error; +} + + +/* + * Find pathname of process's current directory. + * + * Use vfs vnode-to-name reverse cache; if that fails, fall back + * to reading directory contents. + */ + +int +linux_getcwd(struct proc *p, struct linux_getcwd_args *args) +{ + struct __getcwd_args bsd; + caddr_t sg, bp, bend, path; + int error, len, lenused; + +#ifdef DEBUG + printf("Linux-emul(%ld): getcwd(%p, %ld)\n", (long)p->p_pid, + args->buf, args->bufsize); +#endif + + sg = stackgap_init(); + bsd.buf = stackgap_alloc(&sg, SPARE_USRSPACE); + bsd.buflen = SPARE_USRSPACE; + error = __getcwd(p, &bsd); + if (!error) { + lenused = strlen(bsd.buf) + 1; + if (lenused <= args->bufsize) { + p->p_retval[0] = lenused; + error = copyout(bsd.buf, args->buf, lenused); + } + else + error = ERANGE; + } else { + len = args->bufsize; + + if (len > MAXPATHLEN*4) + len = MAXPATHLEN*4; + else if (len < 2) + return ERANGE; + + path = (char *)malloc(len, M_TEMP, M_WAITOK); + + bp = &path[len]; + bend = bp; + *(--bp) = '\0'; + + /* + * 5th argument here is "max number of vnodes to traverse". + * Since each entry takes up at least 2 bytes in the output buffer, + * limit it to N/2 vnodes for an N byte buffer. + */ + + error = linux_getcwd_common (p->p_fd->fd_cdir, NULL, + &bp, path, len/2, GETCWD_CHECK_ACCESS, p); + + if (error) + goto out; + lenused = bend - bp; + p->p_retval[0] = lenused; + /* put the result into user buffer */ + error = copyout(bp, args->buf, lenused); + +out: + free(path, M_TEMP); + } + return (error); +} + diff --git a/sys/conf/files.alpha b/sys/conf/files.alpha index c0f834a4e29..9bad10fedeb 100644 --- a/sys/conf/files.alpha +++ b/sys/conf/files.alpha @@ -170,6 +170,7 @@ alpha/tlsb/tlsbcpu.c optional dec_kn8ae alpha/tlsb/tlsbmem.c optional dec_kn8ae alpha/tlsb/zs_tlsb.c optional dec_kn8ae compat/linux/linux_file.c optional compat_linux +compat/linux/linux_getcwd.c optional compat_linux compat/linux/linux_ioctl.c optional compat_linux compat/linux/linux_ipc.c optional compat_linux compat/linux/linux_mib.c optional compat_linux diff --git a/sys/conf/files.i386 b/sys/conf/files.i386 index 04920ab043c..4f24805423d 100644 --- a/sys/conf/files.i386 +++ b/sys/conf/files.i386 @@ -57,6 +57,7 @@ trlld.o optional oltr \ no-implicit-rule # compat/linux/linux_file.c optional compat_linux +compat/linux/linux_getcwd.c optional compat_linux compat/linux/linux_ioctl.c optional compat_linux compat/linux/linux_ipc.c optional compat_linux compat/linux/linux_mib.c optional compat_linux diff --git a/sys/modules/linux/Makefile b/sys/modules/linux/Makefile index c510b94940a..3360a40da5a 100644 --- a/sys/modules/linux/Makefile +++ b/sys/modules/linux/Makefile @@ -5,8 +5,8 @@ MAINTAINER= marcel@FreeBSD.org .PATH: ${.CURDIR}/../../compat/linux ${.CURDIR}/../../${MACHINE_ARCH}/linux KMOD= linux -SRCS= linux_file.c linux_ioctl.c linux_ipc.c linux_machdep.c linux_misc.c \ - linux_signal.c linux_socket.c linux_stats.c linux_mib.c \ +SRCS= linux_file.c linux_getcwd.c linux_ioctl.c linux_ipc.c linux_machdep.c \ + linux_misc.c linux_signal.c linux_socket.c linux_stats.c linux_mib.c \ linux_dummy.c linux_sysent.c linux_sysvec.c linux_util.c \ opt_compat.h opt_linux.h opt_vmpage.h vnode_if.h OBJS= linux_locore.o