linux_getcwd.c revision 102872
182518Sgallatin/* $FreeBSD: head/sys/compat/linux/linux_getcwd.c 102872 2002-09-02 22:46:05Z iedowse $ */
282518Sgallatin/* $OpenBSD: linux_getcwd.c,v 1.2 2001/05/16 12:50:21 ho Exp $ */
382518Sgallatin/* $NetBSD: vfs_getcwd.c,v 1.3.2.3 1999/07/11 10:24:09 sommerfeld Exp $ */
482518Sgallatin
582518Sgallatin/*-
682518Sgallatin * Copyright (c) 1999 The NetBSD Foundation, Inc.
782518Sgallatin * All rights reserved.
882518Sgallatin *
982518Sgallatin * This code is derived from software contributed to The NetBSD Foundation
1082518Sgallatin * by Bill Sommerfeld.
1182518Sgallatin *
1282518Sgallatin * Redistribution and use in source and binary forms, with or without
1382518Sgallatin * modification, are permitted provided that the following conditions
1482518Sgallatin * are met:
1582518Sgallatin * 1. Redistributions of source code must retain the above copyright
1682518Sgallatin *    notice, this list of conditions and the following disclaimer.
1782518Sgallatin * 2. Redistributions in binary form must reproduce the above copyright
1882518Sgallatin *    notice, this list of conditions and the following disclaimer in the
1982518Sgallatin *    documentation and/or other materials provided with the distribution.
2082518Sgallatin * 3. All advertising materials mentioning features or use of this software
2182518Sgallatin *    must display the following acknowledgement:
2282518Sgallatin *        This product includes software developed by the NetBSD
2382518Sgallatin *        Foundation, Inc. and its contributors.
2482518Sgallatin * 4. Neither the name of The NetBSD Foundation nor the names of its
2582518Sgallatin *    contributors may be used to endorse or promote products derived
2682518Sgallatin *    from this software without specific prior written permission.
2782518Sgallatin *
2882518Sgallatin * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
2982518Sgallatin * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
3082518Sgallatin * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
3182518Sgallatin * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
3282518Sgallatin * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
3382518Sgallatin * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
3482518Sgallatin * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
3582518Sgallatin * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
3682518Sgallatin * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
3782518Sgallatin * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
3882518Sgallatin * POSSIBILITY OF SUCH DAMAGE.
3982518Sgallatin */
4082518Sgallatin#include "opt_compat.h"
41101189Srwatson#include "opt_mac.h"
4282518Sgallatin
4382518Sgallatin#include <sys/param.h>
4482518Sgallatin#include <sys/systm.h>
4582518Sgallatin#include <sys/namei.h>
4682518Sgallatin#include <sys/filedesc.h>
4782518Sgallatin#include <sys/kernel.h>
4882518Sgallatin#include <sys/file.h>
4982518Sgallatin#include <sys/stat.h>
50102872Siedowse#include <sys/syscallsubr.h>
5182518Sgallatin#include <sys/vnode.h>
5282518Sgallatin#include <sys/mount.h>
5382518Sgallatin#include <sys/proc.h>
5482518Sgallatin#include <sys/uio.h>
55101189Srwatson#include <sys/mac.h>
5682518Sgallatin#include <sys/malloc.h>
5782518Sgallatin#include <sys/dirent.h>
5882518Sgallatin#include <ufs/ufs/dir.h>	/* XXX only for DIRBLKSIZ */
5982518Sgallatin
6082518Sgallatin#include <machine/../linux/linux.h>
6182518Sgallatin#include <machine/../linux/linux_proto.h>
6282518Sgallatin#include <compat/linux/linux_util.h>
6382518Sgallatin
6482518Sgallatinstatic int
6592761Salfredlinux_getcwd_scandir(struct vnode **, struct vnode **,
6692761Salfred    char **, char *, struct thread *);
6782518Sgallatinstatic int
6892761Salfredlinux_getcwd_common(struct vnode *, struct vnode *,
6992761Salfred		   char **, char *, int, int, struct thread *);
7082518Sgallatin
7182518Sgallatin#define DIRENT_MINSIZE (sizeof(struct dirent) - (MAXNAMLEN+1) + 4)
7282518Sgallatin
7382518Sgallatin/*
7482518Sgallatin * Vnode variable naming conventions in this file:
7582518Sgallatin *
7682518Sgallatin * rvp: the current root we're aiming towards.
7782518Sgallatin * lvp, *lvpp: the "lower" vnode
7882518Sgallatin * uvp, *uvpp: the "upper" vnode.
7982518Sgallatin *
8082518Sgallatin * Since all the vnodes we're dealing with are directories, and the
8182518Sgallatin * lookups are going *up* in the filesystem rather than *down*, the
8282518Sgallatin * usual "pvp" (parent) or "dvp" (directory) naming conventions are
8382518Sgallatin * too confusing.
8482518Sgallatin */
8582518Sgallatin
8682518Sgallatin/*
8782518Sgallatin * XXX Will infinite loop in certain cases if a directory read reliably
8882518Sgallatin *	returns EINVAL on last block.
8982518Sgallatin * XXX is EINVAL the right thing to return if a directory is malformed?
9082518Sgallatin */
9182518Sgallatin
9282518Sgallatin/*
9382518Sgallatin * XXX Untested vs. mount -o union; probably does the wrong thing.
9482518Sgallatin */
9582518Sgallatin
9682518Sgallatin/*
9782518Sgallatin * Find parent vnode of *lvpp, return in *uvpp
9882518Sgallatin *
9982518Sgallatin * If we care about the name, scan it looking for name of directory
10082518Sgallatin * entry pointing at lvp.
10182518Sgallatin *
10282518Sgallatin * Place the name in the buffer which starts at bufp, immediately
10382518Sgallatin * before *bpp, and move bpp backwards to point at the start of it.
10482518Sgallatin *
10582518Sgallatin * On entry, *lvpp is a locked vnode reference; on exit, it is vput and NULL'ed
10682518Sgallatin * On exit, *uvpp is either NULL or is a locked vnode reference.
10782518Sgallatin */
10882518Sgallatinstatic int
10983366Sjulianlinux_getcwd_scandir(lvpp, uvpp, bpp, bufp, td)
11082518Sgallatin	struct vnode **lvpp;
11182518Sgallatin	struct vnode **uvpp;
11282518Sgallatin	char **bpp;
11382518Sgallatin	char *bufp;
11483366Sjulian	struct thread *td;
11582518Sgallatin{
11682518Sgallatin	int     error = 0;
11782518Sgallatin	int     eofflag;
11882518Sgallatin	off_t   off;
11982518Sgallatin	int     tries;
12082518Sgallatin	struct uio uio;
12182518Sgallatin	struct iovec iov;
12282518Sgallatin	char   *dirbuf = NULL;
12382518Sgallatin	int	dirbuflen;
12482518Sgallatin	ino_t   fileno;
12582518Sgallatin	struct vattr va;
12682518Sgallatin	struct vnode *uvp = NULL;
12782518Sgallatin	struct vnode *lvp = *lvpp;
12882518Sgallatin	struct componentname cn;
12982518Sgallatin	int len, reclen;
13082518Sgallatin	tries = 0;
13182518Sgallatin
13282518Sgallatin	/*
13382518Sgallatin	 * If we want the filename, get some info we need while the
13482518Sgallatin	 * current directory is still locked.
13582518Sgallatin	 */
13682518Sgallatin	if (bufp != NULL) {
13791406Sjhb		error = VOP_GETATTR(lvp, &va, td->td_ucred, td);
13882518Sgallatin		if (error) {
13982518Sgallatin			vput(lvp);
14082518Sgallatin			*lvpp = NULL;
14182518Sgallatin			*uvpp = NULL;
14282518Sgallatin			return error;
14382518Sgallatin		}
14482518Sgallatin	}
14582518Sgallatin
14682518Sgallatin	/*
14782518Sgallatin	 * Ok, we have to do it the hard way..
14882518Sgallatin	 * Next, get parent vnode using lookup of ..
14982518Sgallatin	 */
15082518Sgallatin	cn.cn_nameiop = LOOKUP;
15182518Sgallatin	cn.cn_flags = ISLASTCN | ISDOTDOT | RDONLY;
15283366Sjulian	cn.cn_thread = td;
15391406Sjhb	cn.cn_cred = td->td_ucred;
15482518Sgallatin	cn.cn_pnbuf = NULL;
15582518Sgallatin	cn.cn_nameptr = "..";
15682518Sgallatin	cn.cn_namelen = 2;
15782518Sgallatin	cn.cn_consume = 0;
15882518Sgallatin
15982518Sgallatin	/*
16082518Sgallatin	 * At this point, lvp is locked and will be unlocked by the lookup.
16182518Sgallatin	 * On successful return, *uvpp will be locked
16282518Sgallatin	 */
16382518Sgallatin	error = VOP_LOOKUP(lvp, uvpp, &cn);
16482518Sgallatin	if (error) {
16582518Sgallatin		vput(lvp);
16682518Sgallatin		*lvpp = NULL;
16782518Sgallatin		*uvpp = NULL;
16882518Sgallatin		return error;
16982518Sgallatin	}
17082518Sgallatin	uvp = *uvpp;
17182518Sgallatin
17282518Sgallatin	/* If we don't care about the pathname, we're done */
17382518Sgallatin	if (bufp == NULL) {
17482518Sgallatin		vrele(lvp);
17582518Sgallatin		*lvpp = NULL;
17682518Sgallatin		return 0;
17782518Sgallatin	}
17882518Sgallatin
17982518Sgallatin	fileno = va.va_fileid;
18082518Sgallatin
18182518Sgallatin	dirbuflen = DIRBLKSIZ;
18282518Sgallatin	if (dirbuflen < va.va_blocksize)
18382518Sgallatin		dirbuflen = va.va_blocksize;
18482518Sgallatin	dirbuf = (char *)malloc(dirbuflen, M_TEMP, M_WAITOK);
18582518Sgallatin
18682518Sgallatin#if 0
18782518Sgallatinunionread:
18882518Sgallatin#endif
18982518Sgallatin	off = 0;
19082518Sgallatin	do {
19182518Sgallatin		/* call VOP_READDIR of parent */
19282518Sgallatin		iov.iov_base = dirbuf;
19382518Sgallatin		iov.iov_len = dirbuflen;
19482518Sgallatin
19582518Sgallatin		uio.uio_iov = &iov;
19682518Sgallatin		uio.uio_iovcnt = 1;
19782518Sgallatin		uio.uio_offset = off;
19882518Sgallatin		uio.uio_resid = dirbuflen;
19982518Sgallatin		uio.uio_segflg = UIO_SYSSPACE;
20082518Sgallatin		uio.uio_rw = UIO_READ;
20183366Sjulian		uio.uio_td = td;
20282518Sgallatin
20382518Sgallatin		eofflag = 0;
20482518Sgallatin
205101189Srwatson#ifdef MAC
206101189Srwatson		error = mac_check_vnode_readdir(td->td_ucred, uvp);
207101189Srwatson		if (error == 0)
208101189Srwatson#endif /* MAC */
209101189Srwatson			error = VOP_READDIR(uvp, &uio, td->td_ucred, &eofflag,
210101189Srwatson			    0, 0);
21182518Sgallatin
21282518Sgallatin		off = uio.uio_offset;
21382518Sgallatin
21482518Sgallatin		/*
21582518Sgallatin		 * Try again if NFS tosses its cookies.
21682518Sgallatin		 * XXX this can still loop forever if the directory is busted
21782518Sgallatin		 * such that the second or subsequent page of it always
21882518Sgallatin		 * returns EINVAL
21982518Sgallatin		 */
22082518Sgallatin		if ((error == EINVAL) && (tries < 3)) {
22182518Sgallatin			off = 0;
22282518Sgallatin			tries++;
22382518Sgallatin			continue;	/* once more, with feeling */
22482518Sgallatin		}
22582518Sgallatin
22682518Sgallatin		if (!error) {
22782518Sgallatin			char   *cpos;
22882518Sgallatin			struct dirent *dp;
22982518Sgallatin
23082518Sgallatin			cpos = dirbuf;
23182518Sgallatin			tries = 0;
23282518Sgallatin
23382518Sgallatin			/* scan directory page looking for matching vnode */
23482518Sgallatin			for (len = (dirbuflen - uio.uio_resid); len > 0; len -= reclen) {
23582518Sgallatin				dp = (struct dirent *) cpos;
23682518Sgallatin				reclen = dp->d_reclen;
23782518Sgallatin
23882518Sgallatin				/* check for malformed directory.. */
23982518Sgallatin				if (reclen < DIRENT_MINSIZE) {
24082518Sgallatin					error = EINVAL;
24182518Sgallatin					goto out;
24282518Sgallatin				}
24382518Sgallatin				/*
24482518Sgallatin				 * XXX should perhaps do VOP_LOOKUP to
24582518Sgallatin				 * check that we got back to the right place,
24682518Sgallatin				 * but getting the locking games for that
24782518Sgallatin				 * right would be heinous.
24882518Sgallatin				 */
24982518Sgallatin				if ((dp->d_type != DT_WHT) &&
25082518Sgallatin				    (dp->d_fileno == fileno)) {
25182518Sgallatin					char *bp = *bpp;
25282518Sgallatin					bp -= dp->d_namlen;
25382518Sgallatin
25482518Sgallatin					if (bp <= bufp) {
25582518Sgallatin						error = ERANGE;
25682518Sgallatin						goto out;
25782518Sgallatin					}
25882518Sgallatin					bcopy(dp->d_name, bp, dp->d_namlen);
25982518Sgallatin					error = 0;
26082518Sgallatin					*bpp = bp;
26182518Sgallatin					goto out;
26282518Sgallatin				}
26382518Sgallatin				cpos += reclen;
26482518Sgallatin			}
26582518Sgallatin		}
26682518Sgallatin	} while (!eofflag);
26782518Sgallatin	error = ENOENT;
26882518Sgallatin
26982518Sgallatinout:
27082518Sgallatin	vrele(lvp);
27182518Sgallatin	*lvpp = NULL;
27282518Sgallatin	free(dirbuf, M_TEMP);
27382518Sgallatin	return error;
27482518Sgallatin}
27582518Sgallatin
27682518Sgallatin
27782518Sgallatin/*
27882518Sgallatin * common routine shared by sys___getcwd() and linux_vn_isunder()
27982518Sgallatin */
28082518Sgallatin
28182518Sgallatin#define GETCWD_CHECK_ACCESS 0x0001
28282518Sgallatin
28382518Sgallatinstatic int
28483366Sjulianlinux_getcwd_common (lvp, rvp, bpp, bufp, limit, flags, td)
28582518Sgallatin	struct vnode *lvp;
28682518Sgallatin	struct vnode *rvp;
28782518Sgallatin	char **bpp;
28882518Sgallatin	char *bufp;
28982518Sgallatin	int limit;
29082518Sgallatin	int flags;
29183366Sjulian	struct thread *td;
29282518Sgallatin{
29383366Sjulian	struct filedesc *fdp = td->td_proc->p_fd;
29482518Sgallatin	struct vnode *uvp = NULL;
29582518Sgallatin	char *bp = NULL;
29682518Sgallatin	int error;
29782518Sgallatin	int perms = VEXEC;
29882518Sgallatin
29982518Sgallatin	if (rvp == NULL) {
30082518Sgallatin		rvp = fdp->fd_rdir;
30182518Sgallatin		if (rvp == NULL)
30282518Sgallatin			rvp = rootvnode;
30382518Sgallatin	}
30482518Sgallatin
30582518Sgallatin	VREF(rvp);
30682518Sgallatin	VREF(lvp);
30782518Sgallatin
30882518Sgallatin	/*
30982518Sgallatin	 * Error handling invariant:
31082518Sgallatin	 * Before a `goto out':
31182518Sgallatin	 *	lvp is either NULL, or locked and held.
31282518Sgallatin	 *	uvp is either NULL, or locked and held.
31382518Sgallatin	 */
31482518Sgallatin
31583366Sjulian	error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY, td);
31682518Sgallatin	if (error) {
31782518Sgallatin		vrele(lvp);
31882518Sgallatin		lvp = NULL;
31982518Sgallatin		goto out;
32082518Sgallatin	}
32182518Sgallatin	if (bufp)
32282518Sgallatin		bp = *bpp;
32382518Sgallatin	/*
32482518Sgallatin	 * this loop will terminate when one of the following happens:
32582518Sgallatin	 *	- we hit the root
32682518Sgallatin	 *	- getdirentries or lookup fails
32782518Sgallatin	 *	- we run out of space in the buffer.
32882518Sgallatin	 */
32982518Sgallatin	if (lvp == rvp) {
33082518Sgallatin		if (bp)
33182518Sgallatin			*(--bp) = '/';
33282518Sgallatin		goto out;
33382518Sgallatin	}
33482518Sgallatin	do {
33582518Sgallatin		if (lvp->v_type != VDIR) {
33682518Sgallatin			error = ENOTDIR;
33782518Sgallatin			goto out;
33882518Sgallatin		}
33982518Sgallatin
34082518Sgallatin		/*
34182518Sgallatin		 * access check here is optional, depending on
34282518Sgallatin		 * whether or not caller cares.
34382518Sgallatin		 */
34482518Sgallatin		if (flags & GETCWD_CHECK_ACCESS) {
34591406Sjhb			error = VOP_ACCESS(lvp, perms, td->td_ucred, td);
34682518Sgallatin			if (error)
34782518Sgallatin				goto out;
34882518Sgallatin			perms = VEXEC|VREAD;
34982518Sgallatin		}
35082518Sgallatin
35182518Sgallatin		/*
35282518Sgallatin		 * step up if we're a covered vnode..
35382518Sgallatin		 */
354101308Sjeff		while (lvp->v_vflag & VV_ROOT) {
35582518Sgallatin			struct vnode *tvp;
35682518Sgallatin
35782518Sgallatin			if (lvp == rvp)
35882518Sgallatin				goto out;
35982518Sgallatin
36082518Sgallatin			tvp = lvp;
36182518Sgallatin			lvp = lvp->v_mount->mnt_vnodecovered;
36282518Sgallatin			vput(tvp);
36382518Sgallatin			/*
36482518Sgallatin			 * hodie natus est radici frater
36582518Sgallatin			 */
36682518Sgallatin			if (lvp == NULL) {
36782518Sgallatin				error = ENOENT;
36882518Sgallatin				goto out;
36982518Sgallatin			}
37082518Sgallatin			VREF(lvp);
37183366Sjulian			error = vn_lock(lvp, LK_EXCLUSIVE | LK_RETRY, td);
37282518Sgallatin			if (error != 0) {
37382518Sgallatin				vrele(lvp);
37482518Sgallatin				lvp = NULL;
37582518Sgallatin				goto out;
37682518Sgallatin			}
37782518Sgallatin		}
37883366Sjulian		error = linux_getcwd_scandir(&lvp, &uvp, &bp, bufp, td);
37982518Sgallatin		if (error)
38082518Sgallatin			goto out;
38182518Sgallatin#if DIAGNOSTIC
38282518Sgallatin		if (lvp != NULL)
38382518Sgallatin			panic("getcwd: oops, forgot to null lvp");
38482518Sgallatin		if (bufp && (bp <= bufp)) {
38582518Sgallatin			panic("getcwd: oops, went back too far");
38682518Sgallatin		}
38782518Sgallatin#endif
38882518Sgallatin		if (bp)
38982518Sgallatin			*(--bp) = '/';
39082518Sgallatin		lvp = uvp;
39182518Sgallatin		uvp = NULL;
39282518Sgallatin		limit--;
39382518Sgallatin	} while ((lvp != rvp) && (limit > 0));
39482518Sgallatin
39582518Sgallatinout:
39682518Sgallatin	if (bpp)
39782518Sgallatin		*bpp = bp;
39882518Sgallatin	if (uvp)
39982518Sgallatin		vput(uvp);
40082518Sgallatin	if (lvp)
40182518Sgallatin		vput(lvp);
40282518Sgallatin	vrele(rvp);
40382518Sgallatin	return error;
40482518Sgallatin}
40582518Sgallatin
40682518Sgallatin
40782518Sgallatin/*
40882518Sgallatin * Find pathname of process's current directory.
40982518Sgallatin *
41082518Sgallatin * Use vfs vnode-to-name reverse cache; if that fails, fall back
41182518Sgallatin * to reading directory contents.
41282518Sgallatin */
41382518Sgallatin
41482518Sgallatinint
41583366Sjulianlinux_getcwd(struct thread *td, struct linux_getcwd_args *args)
41682518Sgallatin{
417102872Siedowse	caddr_t bp, bend, path;
41882518Sgallatin	int error, len, lenused;
41982518Sgallatin
42082518Sgallatin#ifdef DEBUG
42183366Sjulian	printf("Linux-emul(%ld): getcwd(%p, %ld)\n", (long)td->td_proc->p_pid,
42286482Speter	       args->buf, (long)args->bufsize);
42382518Sgallatin#endif
42482518Sgallatin
425102872Siedowse	len = args->bufsize;
426102872Siedowse
427102872Siedowse	if (len > MAXPATHLEN*4)
428102872Siedowse		len = MAXPATHLEN*4;
429102872Siedowse	else if (len < 2)
430102872Siedowse		return ERANGE;
431102872Siedowse
432102872Siedowse	path = (char *)malloc(len, M_TEMP, M_WAITOK);
433102872Siedowse
434102872Siedowse	error = kern___getcwd(td, path, UIO_SYSSPACE, len);
43582518Sgallatin	if (!error) {
436102872Siedowse		lenused = strlen(path) + 1;
43782518Sgallatin		if (lenused <= args->bufsize) {
43883366Sjulian			td->td_retval[0] = lenused;
439102872Siedowse			error = copyout(path, args->buf, lenused);
44082518Sgallatin		}
44182518Sgallatin		else
44282518Sgallatin			error = ERANGE;
44382518Sgallatin	} else {
44482518Sgallatin		bp = &path[len];
44582518Sgallatin		bend = bp;
44682518Sgallatin		*(--bp) = '\0';
44782518Sgallatin
44882518Sgallatin		/*
44982518Sgallatin		 * 5th argument here is "max number of vnodes to traverse".
45082518Sgallatin		 * Since each entry takes up at least 2 bytes in the output buffer,
45182518Sgallatin		 * limit it to N/2 vnodes for an N byte buffer.
45282518Sgallatin		 */
45382518Sgallatin
45483366Sjulian		error = linux_getcwd_common (td->td_proc->p_fd->fd_cdir, NULL,
45583366Sjulian		    &bp, path, len/2, GETCWD_CHECK_ACCESS, td);
45682518Sgallatin
45782518Sgallatin		if (error)
45882518Sgallatin			goto out;
45982518Sgallatin		lenused = bend - bp;
46083366Sjulian		td->td_retval[0] = lenused;
46182518Sgallatin		/* put the result into user buffer */
46282518Sgallatin		error = copyout(bp, args->buf, lenused);
463102872Siedowse	}
46482518Sgallatinout:
465102872Siedowse	free(path, M_TEMP);
46682518Sgallatin	return (error);
46782518Sgallatin}
46882518Sgallatin
469