diff options
author | marcus <marcus@FreeBSD.org> | 2009-03-08 19:05:53 +0000 |
---|---|---|
committer | marcus <marcus@FreeBSD.org> | 2009-03-08 19:05:53 +0000 |
commit | 399996bc30b5483814c38a0591e585f0cbe8d4cf (patch) | |
tree | 1ee7e0cf0c231c3fc97b2ac4472f681c6c17351e /sys | |
parent | 5dd07919448d34998c1ec92a5ae00e72b94ae323 (diff) | |
download | FreeBSD-src-399996bc30b5483814c38a0591e585f0cbe8d4cf.zip FreeBSD-src-399996bc30b5483814c38a0591e585f0cbe8d4cf.tar.gz |
Add a default implementation for VOP_VPTOCNP(9) which scans the parent
directory of a vnode to find a dirent with a matching file number. The
name from that dirent is then used to provide the component name.
Note: if the initial vnode argument is not a directory itself, then
the default VOP_VPTOCNP(9) implementation still returns ENOENT.
Reviewed by: kib
Approved by: kib
Tested by: pho
Diffstat (limited to 'sys')
-rw-r--r-- | sys/kern/vfs_default.c | 236 |
1 files changed, 235 insertions, 1 deletions
diff --git a/sys/kern/vfs_default.c b/sys/kern/vfs_default.c index e047a90..1001497 100644 --- a/sys/kern/vfs_default.c +++ b/sys/kern/vfs_default.c @@ -48,8 +48,11 @@ __FBSDID("$FreeBSD$"); #include <sys/malloc.h> #include <sys/mount.h> #include <sys/mutex.h> +#include <sys/namei.h> +#include <sys/fcntl.h> #include <sys/unistd.h> #include <sys/vnode.h> +#include <sys/dirent.h> #include <sys/poll.h> #include <vm/vm.h> @@ -63,6 +66,14 @@ __FBSDID("$FreeBSD$"); static int vop_nolookup(struct vop_lookup_args *); static int vop_nostrategy(struct vop_strategy_args *); +static int get_next_dirent(struct vnode *vp, struct dirent **dpp, + char *dirbuf, int dirbuflen, off_t *off, + char **cpos, int *len, int *eofflag, + struct thread *td); +static int dirent_exists(struct vnode *vp, const char *dirname, + struct thread *td); + +#define DIRENT_MINSIZE (sizeof(struct dirent) - (MAXNAMLEN+1) + 4) /* * This vnode table stores what we want to do if the filesystem doesn't @@ -98,7 +109,7 @@ struct vop_vector default_vnodeops = { .vop_revoke = VOP_PANIC, .vop_strategy = vop_nostrategy, .vop_unlock = vop_stdunlock, - .vop_vptocnp = VOP_ENOENT, + .vop_vptocnp = vop_stdvptocnp, .vop_vptofh = vop_stdvptofh, }; @@ -210,6 +221,108 @@ vop_nostrategy (struct vop_strategy_args *ap) return (EOPNOTSUPP); } +static int +get_next_dirent(struct vnode *vp, struct dirent **dpp, char *dirbuf, + int dirbuflen, off_t *off, char **cpos, int *len, + int *eofflag, struct thread *td) +{ + int error, reclen; + struct uio uio; + struct iovec iov; + struct dirent *dp; + + KASSERT(VOP_ISLOCKED(vp), ("vp %p is not locked", vp)); + KASSERT(vp->v_type == VDIR, ("vp %p is not a directory", vp)); + + if (*len == 0) { + 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_td = td; + + *eofflag = 0; + +#ifdef MAC + error = mac_vnode_check_readdir(td->td_ucred, vp); + if (error == 0) +#endif + error = VOP_READDIR(vp, &uio, td->td_ucred, eofflag, + NULL, NULL); + if (error) + return (error); + + *off = uio.uio_offset; + + *cpos = dirbuf; + *len = (dirbuflen - uio.uio_resid); + } + + dp = (struct dirent *)(*cpos); + reclen = dp->d_reclen; + *dpp = dp; + + /* check for malformed directory.. */ + if (reclen < DIRENT_MINSIZE) + return (EINVAL); + + *cpos += reclen; + *len -= reclen; + + return (0); +} + +/* + * Check if a named file exists in a given directory vnode. + */ +static int +dirent_exists(struct vnode *vp, const char *dirname, struct thread *td) +{ + char *dirbuf, *cpos; + int error, eofflag, dirbuflen, len, found; + off_t off; + struct dirent *dp; + struct vattr va; + + KASSERT(VOP_ISLOCKED(vp), ("vp %p is not locked", vp)); + KASSERT(vp->v_type == VDIR, ("vp %p is not a directory", vp)); + + found = 0; + + error = VOP_GETATTR(vp, &va, td->td_ucred); + if (error) + return (found); + + dirbuflen = DEV_BSIZE; + if (dirbuflen < va.va_blocksize) + dirbuflen = va.va_blocksize; + dirbuf = (char *)malloc(dirbuflen, M_TEMP, M_WAITOK); + + off = 0; + len = 0; + do { + error = get_next_dirent(vp, &dp, dirbuf, dirbuflen, &off, + &cpos, &len, &eofflag, td); + if (error) + goto out; + + if ((dp->d_type != DT_WHT) && + !strcmp(dp->d_name, dirname)) { + found = 1; + goto out; + } + } while (len > 0 || !eofflag); + +out: + free(dirbuf, M_TEMP); + return (found); +} + /* * Advisory record locking support */ @@ -557,6 +670,127 @@ vop_stdvptofh(struct vop_vptofh_args *ap) return (EOPNOTSUPP); } +int +vop_stdvptocnp(struct vop_vptocnp_args *ap) +{ + struct vnode *vp = ap->a_vp; + struct vnode **dvp = ap->a_vpp; + char *buf = ap->a_buf; + int *buflen = ap->a_buflen; + char *dirbuf, *cpos; + int i, error, eofflag, dirbuflen, flags, locked, len, covered; + off_t off; + ino_t fileno; + struct vattr va; + struct nameidata nd; + struct thread *td; + struct dirent *dp; + struct vnode *mvp; + + i = *buflen; + error = 0; + covered = 0; + td = curthread; + + if (vp->v_type != VDIR) + return (ENOENT); + + error = VOP_GETATTR(vp, &va, td->td_ucred); + if (error) + return (error); + + VREF(vp); + locked = VOP_ISLOCKED(vp); + VOP_UNLOCK(vp, 0); + NDINIT_ATVP(&nd, LOOKUP, FOLLOW | LOCKLEAF, UIO_SYSSPACE, + "..", vp, td); + flags = FREAD; + error = vn_open(&nd, &flags, 0, NULL); + if (error) { + vn_lock(vp, locked | LK_RETRY); + return (error); + } + NDFREE(&nd, NDF_ONLY_PNBUF); + + mvp = *dvp = nd.ni_vp; + + if (vp->v_mount != (*dvp)->v_mount && + ((*dvp)->v_vflag & VV_ROOT) && + ((*dvp)->v_mount->mnt_flag & MNT_UNION)) { + *dvp = (*dvp)->v_mount->mnt_vnodecovered; + VREF(mvp); + VOP_UNLOCK(mvp, 0); + vn_close(mvp, FREAD, td->td_ucred, td); + VREF(*dvp); + vn_lock(*dvp, LK_EXCLUSIVE | LK_RETRY); + covered = 1; + } + + fileno = va.va_fileid; + + dirbuflen = DEV_BSIZE; + if (dirbuflen < va.va_blocksize) + dirbuflen = va.va_blocksize; + dirbuf = (char *)malloc(dirbuflen, M_TEMP, M_WAITOK); + + if ((*dvp)->v_type != VDIR) { + error = ENOENT; + goto out; + } + + off = 0; + len = 0; + do { + /* call VOP_READDIR of parent */ + error = get_next_dirent(*dvp, &dp, dirbuf, dirbuflen, &off, + &cpos, &len, &eofflag, td); + if (error) + goto out; + + if ((dp->d_type != DT_WHT) && + (dp->d_fileno == fileno)) { + if (covered) { + VOP_UNLOCK(*dvp, 0); + vn_lock(mvp, LK_EXCLUSIVE | LK_RETRY); + if (dirent_exists(mvp, dp->d_name, td)) { + error = ENOENT; + VOP_UNLOCK(mvp, 0); + vn_lock(*dvp, LK_EXCLUSIVE | LK_RETRY); + goto out; + } + VOP_UNLOCK(mvp, 0); + vn_lock(*dvp, LK_EXCLUSIVE | LK_RETRY); + } + i -= dp->d_namlen; + + if (i < 0) { + error = ENOMEM; + goto out; + } + bcopy(dp->d_name, buf + i, dp->d_namlen); + error = 0; + goto out; + } + } while (len > 0 || !eofflag); + error = ENOENT; + +out: + free(dirbuf, M_TEMP); + if (!error) { + *buflen = i; + vhold(*dvp); + } + if (covered) { + vput(*dvp); + vrele(mvp); + } else { + VOP_UNLOCK(mvp, 0); + vn_close(mvp, FREAD, td->td_ucred, td); + } + vn_lock(vp, locked | LK_RETRY); + return (error); +} + /* * vfs default ops * used to fill the vfs function table to get reasonable default return values. |