From: jasoni Date: Mon, 27 Mar 2000 22:38:11 +0000 (+0000) Subject: Implement getcwd. X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=1d88947afacb62b2a8e9e9a98efb9d0efc2e5088;p=openbsd Implement getcwd. --- diff --git a/sys/compat/linux/files.linux b/sys/compat/linux/files.linux index 6590748ad2f..e907dffad6e 100644 --- a/sys/compat/linux/files.linux +++ b/sys/compat/linux/files.linux @@ -1,4 +1,4 @@ -# $OpenBSD: files.linux,v 1.7 1999/11/10 15:55:21 mickey Exp $ +# $OpenBSD: files.linux,v 1.8 2000/03/27 22:38:11 jasoni Exp $ # $NetBSD: files.linux,v 1.4 1996/03/08 04:55:59 mycroft Exp $ # # Config.new file description for machine-independent Linux compat code. @@ -20,3 +20,4 @@ file compat/linux/linux_syscalls.c compat_linux & syscall_debug file compat/linux/linux_sysent.c compat_linux file compat/linux/linux_termios.c compat_linux file compat/linux/linux_mount.c compat_linux +file compat/linux/linux_getcwd.c compat_linux diff --git a/sys/compat/linux/linux_getcwd.c b/sys/compat/linux/linux_getcwd.c new file mode 100644 index 00000000000..6a66a2642c1 --- /dev/null +++ b/sys/compat/linux/linux_getcwd.c @@ -0,0 +1,613 @@ +/* $OpenBSD: linux_getcwd.c,v 1.1 2000/03/27 22:38:12 jasoni 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +int proc_isunder __P((struct proc *, struct proc*)); /* missing from proc.h */ +#include +#include +#include +#include /* XXX only for DIRBLKSIZ */ + +#include + +#include +#include +#include +#include + +#include + +static int +linux_getcwd_scandir __P((struct vnode **, struct vnode **, + char **, char *, struct proc *)); +static int +linux_getcwd_getcache __P((struct vnode **, struct vnode **, + char **, char *)); +static int +linux_getcwd_common __P((struct vnode *, struct vnode *, + char **, char *, int, int, struct proc *)); + +static int +linux_vn_isunder __P((struct vnode *, struct vnode *, 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_hash = 0; + 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); +#if 0 + /* + * Deal with mount -o union, which unions only the + * root directory of the mount. + */ + if ((uvp->v_flag & VROOT) && + (uvp->v_mount->mnt_flag & MNT_UNION)) { + struct vnode *tvp = uvp; + uvp = uvp->v_mount->mnt_vnodecovered; + vput(tvp); + VREF(uvp); + *uvpp = uvp; + error = vn_lock(uvp, LK_EXCLUSIVE | LK_RETRY); + if (error != 0) { + vrele(uvp); + *uvpp = uvp = NULL; + goto out; + } + goto unionread; + } +#endif + error = ENOENT; + +out: + vrele(lvp); + *lvpp = NULL; + free(dirbuf, M_TEMP); + return error; +} + +/* + * Look in the vnode-to-name reverse cache to see if + * we can find things the easy way. + * + * XXX vget failure path is untested. + * + * On entry, *lvpp is a locked vnode reference. + * On exit, one of the following is the case: + * 0) Both *lvpp and *uvpp are NULL and failure is returned. + * 1) *uvpp is NULL, *lvpp remains locked and -1 is returned (cache miss) + * 2) *uvpp is a locked vnode reference, *lvpp is vput and NULL'ed + * and 0 is returned (cache hit) + */ + +static int +linux_getcwd_getcache(lvpp, uvpp, bpp, bufp) + struct vnode **lvpp, **uvpp; + char **bpp; + char *bufp; +{ +#ifdef notyet + struct vnode *lvp, *uvp = NULL; + int error; + int vpid; + + lvp = *lvpp; + + /* + * This returns 0 on a cache hit, -1 on a clean cache miss, + * or an errno on other failure. + */ + error = cache_revlookup(lvp, uvpp, bpp, bufp); + if (error) { + if (error != -1) { + vput(lvp); + *lvpp = NULL; + *uvpp = NULL; + } + return error; + } + uvp = *uvpp; + vpid = uvp->v_id; + + /* + * Since we're going up, we have to release the current lock + * before we take the parent lock. + */ + + VOP_UNLOCK(lvp, 0); + + error = vget(uvp, LK_EXCLUSIVE | LK_RETRY); + if (error != 0) + *uvpp = NULL; + /* + * Verify that vget succeeded, and check that vnode capability + * didn't change while we were waiting for the lock. + */ + if (error || (vpid != uvp->v_id)) { + /* + * Oops, we missed. If the vget failed, or the + * capability changed, try to get our lock back; if + * that works, tell caller to try things the hard way, + * otherwise give up. + */ + if (!error) vput(uvp); + *uvpp = NULL; + + error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY); + + if (!error) + return -1; + } + vrele(lvp); + *lvpp = NULL; + return error; +#endif /* notyet */ + return (-1); +} + +/* + * 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; + } + } + /* + * Look in the name cache; if that fails, look in the + * directory.. + */ + error = linux_getcwd_getcache(&lvp, &uvp, &bp, bufp); + if (error == -1) + 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; +} + +/* + * Check if one directory can be found inside another in the directory + * hierarchy. + * + * Intended to be used in chroot, chdir, fchdir, etc., to ensure that + * chroot() actually means something. + */ +static int +linux_vn_isunder(lvp, rvp, p) + struct vnode *lvp; + struct vnode *rvp; + struct proc *p; +{ + int error; + + error = linux_getcwd_common (lvp, rvp, NULL, NULL, MAXPATHLEN/2, 0, p); + + if (!error) + return 1; + else + return 0; +} + +/* + * Returns true if proc p1's root directory equal to or under p2's + * root directory. + * + * Intended to be used from ptrace/procfs sorts of things. + */ + +int proc_isunder (p1, p2) + struct proc *p1; + struct proc *p2; +{ + struct vnode *r1 = p1->p_fd->fd_rdir; + struct vnode *r2 = p2->p_fd->fd_rdir; + + if (r1 == NULL) + return (r2 == NULL); + else if (r2 == NULL) + return 1; + else + return linux_vn_isunder(r1, r2, p2); +} + +/* + * 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_sys_getcwd(p, v, retval) + struct proc *p; + void *v; + register_t *retval; +{ + register struct linux_sys_getcwd_args /* { + syscallarg(char *) bufp; + syscallarg(size_t) length; + } */ *uap = v; + + int error; + char *path; + char *bp, *bend; + int len = SCARG(uap, length); + int lenused; + + if (len > MAXPATHLEN*4) + len = MAXPATHLEN*4; + else if (len < 2) + return ERANGE; + + path = (char *)malloc(len, M_TEMP, M_WAITOK); + if (!path) + return ENOMEM; + + 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; + *retval = lenused; + /* put the result into user buffer */ + error = copyout(bp, SCARG(uap, bufp), lenused); + +out: + free(path, M_TEMP); + return error; +} diff --git a/sys/compat/linux/syscalls.master b/sys/compat/linux/syscalls.master index bbbba4298c6..944da370a8e 100644 --- a/sys/compat/linux/syscalls.master +++ b/sys/compat/linux/syscalls.master @@ -1,4 +1,4 @@ - $OpenBSD: syscalls.master,v 1.18 2000/03/18 21:36:02 ian Exp $ + $OpenBSD: syscalls.master,v 1.19 2000/03/27 22:38:12 jasoni Exp $ ; $NetBSD: syscalls.master,v 1.15 1995/12/18 14:35:10 fvdl Exp $ ; @(#)syscalls.master 8.1 (Berkeley) 7/19/93 @@ -268,8 +268,6 @@ 168 NOARGS { int sys_poll(struct pollfd *fds, u_int nfds, \ int timeout); } 169 UNIMPL nfsservctl -;170 UNIMPL setresgid -;171 UNIMPL getresgid 170 STD { int linux_sys_setresgid(gid_t rgid, gid_t egid, \ gid_t sgid); } 171 STD { int linux_sys_getresgid(gid_t *rgid, gid_t *egid, \ @@ -292,7 +290,7 @@ 180 UNIMPL pread 181 UNIMPL pwrite 182 STD { int linux_sys_chown(char *path, int uid, int gid); } -183 UNIMPL getcwd +183 STD { int linux_sys_getcwd(char *bufp, size_t length); } 184 UNIMPL capget 185 UNIMPL capset 186 UNIMPL sigaltstack