Date: Tue, 15 Jun 1999 01:39:14 -0700 (PDT) From: Matthew Dillon <dillon@apollo.backplane.com> To: Ville-Pertti Keinonen <will@iki.fi> Cc: current@freebsd.org Subject: Re: NFS vnode reference issues on server Message-ID: <199906150839.BAA15539@apollo.backplane.com> References: <199906150421.VAA14439@apollo.backplane.com.newsgate.clinet.fi> <86d7yxoqj6.fsf@not.demophon.com>
next in thread | previous in thread | raw e-mail | index | archive | help
:Matthew Dillon <dillon@apollo.backplane.com> writes:
:
:> Ok, something for people following the code to look over if they have
:> the time. This in nfs_subs.c, nfs_namei(). Question: ndp->ni_vp
:> is non-NULL and appears to be referenced as of the time a badlink
:> occurs, linklen is 0, or the link is too long. Do we have to
:> release ndp->ni_vp and NULL it out in this case? I believe so.
:
:Yes, and it would seem that you should also do this for the previous
:error return (ELOOP). There are no special cases for specific errors
:so all exits with error conditions should behave identically. Callers
:bail out without doing anything further with the nameidata on errors,
:apparently in all cases.
:
:A reference to the directory vnode appears to be lost in most cases of
:errors, as well (once again, the reference in the nameidata). In
:*some* cases, there is a reference in retdirp, but it's a different
:reference.
Yup. I think I caught them all. Here is a new preliminary nfs_namei().
I think I caught all the weird cases. The ref counts related to
*retdirp weren't broken, but they were confusing. ELOOP was broken
too ... I had missed that.
(Anyone who wants to) Take a look at this one and tell me what you think.
-Matt
Matthew Dillon
<dillon@backplane.com>
#ifndef NFS_NOSERVER
/*
* Set up nameidata for a lookup() call and do it.
*
* If pubflag is set, this call is done for a lookup operation on the
* public filehandle. In that case we allow crossing mountpoints and
* absolute pathnames. However, the caller is expected to check that
* the lookup result is within the public fs, and deny access if
* it is not.
*
* If an error is returned, ndp->ni_vp will be left NULL.
*
* dirp may be set whether an error is returned or not, and must be
* released by the caller.
*/
int
nfs_namei(ndp, fhp, len, slp, nam, mdp, dposp, retdirp, p, kerbflag, pubflag)
register struct nameidata *ndp;
fhandle_t *fhp;
int len;
struct nfssvc_sock *slp;
struct sockaddr *nam;
struct mbuf **mdp;
caddr_t *dposp;
struct vnode **retdirp;
struct proc *p;
int kerbflag, pubflag;
{
register int i, rem;
register struct mbuf *md;
register char *fromcp, *tocp, *cp;
struct iovec aiov;
struct uio auio;
struct vnode *dp;
int error, rdonly, linklen;
struct componentname *cnp = &ndp->ni_cnd;
*retdirp = (struct vnode *)0;
cnp->cn_pnbuf = zalloc(namei_zone);
/*
* Copy the name from the mbuf list to ndp->ni_pnbuf
* and set the various ndp fields appropriately.
*/
fromcp = *dposp;
tocp = cnp->cn_pnbuf;
md = *mdp;
rem = mtod(md, caddr_t) + md->m_len - fromcp;
cnp->cn_hash = 0;
for (i = 0; i < len; i++) {
while (rem == 0) {
md = md->m_next;
if (md == NULL) {
error = EBADRPC;
goto out;
}
fromcp = mtod(md, caddr_t);
rem = md->m_len;
}
if (*fromcp == '\0' || (!pubflag && *fromcp == '/')) {
error = EACCES;
goto out;
}
cnp->cn_hash += (unsigned char)*fromcp;
*tocp++ = *fromcp++;
rem--;
}
*tocp = '\0';
*mdp = md;
*dposp = fromcp;
len = nfsm_rndup(len)-len;
if (len > 0) {
if (rem >= len)
*dposp += len;
else if ((error = nfs_adv(mdp, dposp, len, rem)) != 0)
goto out;
}
/*
* Extract and set starting directory.
*/
error = nfsrv_fhtovp(fhp, FALSE, &dp, ndp->ni_cnd.cn_cred, slp,
nam, &rdonly, kerbflag, pubflag);
if (error)
goto out;
if (dp->v_type != VDIR) {
vrele(dp);
error = ENOTDIR;
goto out;
}
if (rdonly)
cnp->cn_flags |= RDONLY;
/*
* Set return directory. Reference to dp is implicitly transfered
* to the returned pointer
*/
*retdirp = dp;
if (pubflag) {
/*
* Oh joy. For WebNFS, handle those pesky '%' escapes,
* and the 'native path' indicator.
*/
cp = zalloc(namei_zone);
fromcp = cnp->cn_pnbuf;
tocp = cp;
if ((unsigned char)*fromcp >= WEBNFS_SPECCHAR_START) {
switch ((unsigned char)*fromcp) {
case WEBNFS_NATIVE_CHAR:
/*
* 'Native' path for us is the same
* as a path according to the NFS spec,
* just skip the escape char.
*/
fromcp++;
break;
/*
* More may be added in the future, range 0x80-0xff
*/
default:
error = EIO;
zfree(namei_zone, cp);
goto out;
}
}
/*
* Translate the '%' escapes, URL-style.
*/
while (*fromcp != '\0') {
if (*fromcp == WEBNFS_ESC_CHAR) {
if (fromcp[1] != '\0' && fromcp[2] != '\0') {
fromcp++;
*tocp++ = HEXSTRTOI(fromcp);
fromcp += 2;
continue;
} else {
error = ENOENT;
zfree(namei_zone, cp);
goto out;
}
} else
*tocp++ = *fromcp++;
}
*tocp = '\0';
zfree(namei_zone, cnp->cn_pnbuf);
cnp->cn_pnbuf = cp;
}
ndp->ni_pathlen = (tocp - cnp->cn_pnbuf) + 1;
ndp->ni_segflg = UIO_SYSSPACE;
if (pubflag) {
ndp->ni_rootdir = rootvnode;
ndp->ni_loopcnt = 0;
if (cnp->cn_pnbuf[0] == '/')
dp = rootvnode;
} else {
cnp->cn_flags |= NOCROSSMOUNT;
}
/*
* Initialize for scan, set ni_startdir and bump ref on dp again
* becuase lookup() will dereference ni_startdir.
*/
cnp->cn_proc = p;
VREF(dp);
ndp->ni_startdir = dp;
for (;;) {
cnp->cn_nameptr = cnp->cn_pnbuf;
/*
* Call lookup() to do the real work. If an error occurs,
* ndp->ni_vp and ni_dvp are left uninitialized or NULL and
* we do not have to dereference anything before returning.
* In either case ni_startdir will be dereferenced and NULLed
* out.
*/
error = lookup(ndp);
if (error)
break;
/*
* Check for encountering a symbolic link. Trivial
* termination occurs if no symlink encountered.
*/
if ((cnp->cn_flags & ISSYMLINK) == 0) {
nfsrv_object_create(ndp->ni_vp);
if (cnp->cn_flags & (SAVENAME | SAVESTART)) {
cnp->cn_flags |= HASBUF;
return (0);
}
/*
* no error, but do not save buffer either. Break
* out of loop ( basically goto out; ).
*/
break;
}
/*
* Validate symlink
*/
if ((cnp->cn_flags & LOCKPARENT) && ndp->ni_pathlen == 1)
VOP_UNLOCK(ndp->ni_dvp, 0, p);
if (!pubflag) {
error = EINVAL;
goto badlink2;
}
if (ndp->ni_loopcnt++ >= MAXSYMLINKS) {
error = ELOOP;
goto badlink2;
}
if (ndp->ni_pathlen > 1)
cp = zalloc(namei_zone);
else
cp = cnp->cn_pnbuf;
aiov.iov_base = cp;
aiov.iov_len = MAXPATHLEN;
auio.uio_iov = &aiov;
auio.uio_iovcnt = 1;
auio.uio_offset = 0;
auio.uio_rw = UIO_READ;
auio.uio_segflg = UIO_SYSSPACE;
auio.uio_procp = (struct proc *)0;
auio.uio_resid = MAXPATHLEN;
error = VOP_READLINK(ndp->ni_vp, &auio, cnp->cn_cred);
if (error) {
badlink1:
if (ndp->ni_pathlen > 1)
zfree(namei_zone, cp);
badlink2:
vrele(ndp->ni_dvp);
vput(ndp->ni_vp);
ndp->ni_vp = NULL;
ndp->ni_dvp = NULL;
break;
}
linklen = MAXPATHLEN - auio.uio_resid;
if (linklen == 0) {
error = ENOENT;
goto badlink1;
}
if (linklen + ndp->ni_pathlen >= MAXPATHLEN) {
error = ENAMETOOLONG;
goto badlink1;
}
/*
* Adjust or replace path
*/
if (ndp->ni_pathlen > 1) {
bcopy(ndp->ni_next, cp + linklen, ndp->ni_pathlen);
zfree(namei_zone, cnp->cn_pnbuf);
cnp->cn_pnbuf = cp;
} else
cnp->cn_pnbuf[linklen] = '\0';
ndp->ni_pathlen += linklen;
/*
* Cleanup refs for next loop and check if root directory
* should replace current directory. Normally ni_dvp
* becomes the new base directory and is cleaned up when
* we loop. Explicitly null pointers after invalidation
* to clarify operation.
*/
vput(ndp->ni_vp);
ndp->ni_vp = NULL;
if (cnp->cn_pnbuf[0] == '/') {
vrele(ndp->ni_dvp);
ndp->ni_dvp = ndp->ni_rootdir;
VREF(ndp->ni_dvp);
}
ndp->ni_startdir = ndp->ni_dvp;
ndp->ni_dvp = NULL;
}
out:
zfree(namei_zone, cnp->cn_pnbuf);
return (error);
}
To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-current" in the body of the message
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?199906150839.BAA15539>
