Index: sys/contrib/openzfs/include/os/freebsd/zfs/sys/zfs_znode_impl.h =================================================================== --- sys/contrib/openzfs/include/os/freebsd/zfs/sys/zfs_znode_impl.h +++ sys/contrib/openzfs/include/os/freebsd/zfs/sys/zfs_znode_impl.h @@ -52,6 +52,7 @@ #define ZNODE_OS_FIELDS \ struct zfsvfs *z_zfsvfs; \ vnode_t *z_vnode; \ + char *z_cached_symlink; \ uint64_t z_uid; \ uint64_t z_gid; \ uint64_t z_gen; \ Index: sys/contrib/openzfs/module/os/freebsd/zfs/zfs_vnops.c =================================================================== --- sys/contrib/openzfs/module/os/freebsd/zfs/zfs_vnops.c +++ sys/contrib/openzfs/module/os/freebsd/zfs/zfs_vnops.c @@ -5273,6 +5273,27 @@ } #endif +#if __FreeBSD_version >= 1300132 +static int +zfs_freebsd_fplookup_symlink(struct vop_fplookup_symlink_args *v) +{ + vnode_t *vp; + znode_t *zp; + char *target; + + vp = v->a_vp; + zp = VTOZ_SMR(vp); + if (__predict_false(zp == NULL)) { + return (EAGAIN); + } + target = atomic_load_ptr(&zp->z_cached_symlink); + if (target == NULL) { + return (EAGAIN); + } + return (cache_symlink_resolve(v->a_fpl, target, strlen(target))); +} +#endif + #ifndef _SYS_SYSPROTO_H_ struct vop_access_args { struct vnode *a_vp; @@ -5759,6 +5780,8 @@ struct componentname *cnp = ap->a_cnp; vattr_t *vap = ap->a_vap; znode_t *zp = NULL; + char *symlink; + size_t symlink_len; int rc; ASSERT(cnp->cn_flags & SAVENAME); @@ -5769,8 +5792,18 @@ rc = zfs_symlink(VTOZ(ap->a_dvp), cnp->cn_nameptr, vap, ap->a_target, &zp, cnp->cn_cred, 0 /* flags */); - if (rc == 0) + if (rc == 0) { *ap->a_vpp = ZTOV(zp); + ASSERT_VOP_ELOCKED(ZTOV(zp), __func__); + MPASS(zp->z_cached_symlink == NULL); + symlink_len = strlen(ap->a_target); + symlink = cache_symlink_alloc(symlink_len, M_WAITOK); + if (symlink != NULL) { + memcpy(symlink, ap->a_target, symlink_len); + atomic_store_rel_ptr((uintptr_t *)&zp->z_cached_symlink, + (uintptr_t)symlink); + } + } return (rc); } @@ -5785,8 +5818,35 @@ static int zfs_freebsd_readlink(struct vop_readlink_args *ap) { - - return (zfs_readlink(ap->a_vp, ap->a_uio, ap->a_cred, NULL)); + znode_t *zp = VTOZ(ap->a_vp); + struct uio *auio; + char *symlink, *base; + size_t symlink_len; + int error; + bool trycache; + + auio = ap->a_uio; + trycache = false; + if (auio->uio_segflg == UIO_SYSSPACE && auio->uio_iovcnt == 1) { + base = auio->uio_iov->iov_base; + symlink_len = auio->uio_iov->iov_len; + trycache = true; + } + error = zfs_readlink(ap->a_vp, auio, ap->a_cred, NULL); + if (atomic_load_ptr(&zp->z_cached_symlink) != NULL || + error != 0 || !trycache) { + return (error); + } + symlink_len -= auio->uio_resid; + symlink = cache_symlink_alloc(symlink_len, M_WAITOK); + if (symlink != NULL) { + memcpy(symlink, base, symlink_len); + if (!atomic_cmpset_rel_ptr((uintptr_t *)&zp->z_cached_symlink, + (uintptr_t)NULL, (uintptr_t)symlink)) { + cache_symlink_free(symlink); + } + } + return (error); } #ifndef _SYS_SYSPROTO_H_ @@ -6532,6 +6592,9 @@ .vop_reclaim = zfs_freebsd_reclaim, #if __FreeBSD_version >= 1300102 .vop_fplookup_vexec = zfs_freebsd_fplookup_vexec, +#endif +#if __FreeBSD_version >= 1300132 + .vop_fplookup_symlink = zfs_freebsd_fplookup_symlink, #endif .vop_access = zfs_freebsd_access, .vop_allocate = VOP_EINVAL, @@ -6581,6 +6644,9 @@ .vop_fsync = zfs_freebsd_fsync, #if __FreeBSD_version >= 1300102 .vop_fplookup_vexec = zfs_freebsd_fplookup_vexec, +#endif +#if __FreeBSD_version >= 1300132 + .vop_fplookup_symlink = zfs_freebsd_fplookup_symlink, #endif .vop_access = zfs_freebsd_access, .vop_getattr = zfs_freebsd_getattr, @@ -6605,6 +6671,7 @@ #if __FreeBSD_version >= 1300121 .vop_fplookup_vexec = VOP_EAGAIN, #endif + .vop_fplookup_symlink = VOP_EAGAIN, .vop_access = zfs_freebsd_access, .vop_inactive = zfs_freebsd_inactive, .vop_reclaim = zfs_freebsd_reclaim, Index: sys/contrib/openzfs/module/os/freebsd/zfs/zfs_znode.c =================================================================== --- sys/contrib/openzfs/module/os/freebsd/zfs/zfs_znode.c +++ sys/contrib/openzfs/module/os/freebsd/zfs/zfs_znode.c @@ -447,6 +447,7 @@ zp->z_blksz = blksz; zp->z_seq = 0x7A4653; zp->z_sync_cnt = 0; + atomic_store_ptr((uintptr_t *)&zp->z_cached_symlink, (uintptr_t)NULL); vp = ZTOV(zp); @@ -1240,6 +1241,7 @@ zfs_znode_free(znode_t *zp) { zfsvfs_t *zfsvfs = zp->z_zfsvfs; + char *symlink; ASSERT(zp->z_sa_hdl == NULL); zp->z_vnode = NULL; @@ -1248,6 +1250,11 @@ list_remove(&zfsvfs->z_all_znodes, zp); zfsvfs->z_nr_znodes--; mutex_exit(&zfsvfs->z_znodes_lock); + symlink = atomic_load_ptr(&zp->z_cached_symlink); + if (symlink != NULL) { + atomic_store_rel_ptr((uintptr_t *)&zp->z_cached_symlink, (uintptr_t)NULL); + cache_symlink_free(symlink); + } if (zp->z_acl_cached) { zfs_acl_free(zp->z_acl_cached); Index: sys/fs/deadfs/dead_vnops.c =================================================================== --- sys/fs/deadfs/dead_vnops.c +++ sys/fs/deadfs/dead_vnops.c @@ -80,6 +80,7 @@ .vop_unset_text = dead_unset_text, .vop_write = dead_write, .vop_fplookup_vexec = VOP_EOPNOTSUPP, + .vop_fplookup_symlink = VOP_EAGAIN, }; VFS_VOP_VECTOR_REGISTER(dead_vnodeops); Index: sys/fs/tmpfs/tmpfs.h =================================================================== --- sys/fs/tmpfs/tmpfs.h +++ sys/fs/tmpfs/tmpfs.h @@ -278,7 +278,10 @@ /* Valid when tn_type == VLNK. */ /* The link's target, allocated from a string pool. */ - char * tn_link; /* (c) */ + struct tn_link { + char * tn_link_target; /* (c) */ + bool tn_link_smr; /* (c) */ + } tn_link; /* Valid when tn_type == VREG. */ struct tn_reg { @@ -301,7 +304,8 @@ #define tn_rdev tn_spec.tn_rdev #define tn_dir tn_spec.tn_dir -#define tn_link tn_spec.tn_link +#define tn_link_target tn_spec.tn_link.tn_link_target +#define tn_link_smr tn_spec.tn_link.tn_link_smr #define tn_reg tn_spec.tn_reg #define tn_fifo tn_spec.tn_fifo Index: sys/fs/tmpfs/tmpfs_subr.c =================================================================== --- sys/fs/tmpfs/tmpfs_subr.c +++ sys/fs/tmpfs/tmpfs_subr.c @@ -252,6 +252,8 @@ { struct tmpfs_node *nnode; vm_object_t obj; + char *symlink; + bool symlink_smr; /* If the root directory of the 'tmp' file system is not yet * allocated, this must be the request to do it. */ @@ -327,9 +329,42 @@ case VLNK: MPASS(strlen(target) < MAXPATHLEN); nnode->tn_size = strlen(target); - nnode->tn_link = malloc(nnode->tn_size, M_TMPFSNAME, - M_WAITOK); - memcpy(nnode->tn_link, target, nnode->tn_size); + + symlink = NULL; + if (!tmp->tm_nonc) { + symlink = cache_symlink_alloc(nnode->tn_size + 1, + M_WAITOK | M_ZERO); + symlink_smr = true; + } + if (symlink == NULL) { + symlink = malloc(nnode->tn_size + 1, M_TMPFSNAME, + M_WAITOK | M_ZERO); + symlink_smr = false; + } + memcpy(symlink, target, nnode->tn_size); + /* + * Allow safe symlink resolving for lockless lookup. + * tmpfs_fplookup_symlink references this comment. + * + * 1. nnode is not yet visible to the world. + * 2. store release of tn_link_smr guarantees that tn_link_target + * is set and the buffer it points to fully populated + * 3. tn_link_target content is immutable until node destruction, + * where the pointer gets set to NULL + * 4. tn_link_smr is never changed once set + * + * As a result anyone obtaining nnode pointer past this point + * and issuing a data dependency barrier is is guaranteed to + * always see either the correct buffer or NULL. The + * tn_link_smr flag may be set to true despite being stale, but + * it will never be set incorrectly if the buffer is present. + * + * Since data dependency barrier is a nop on all supported + * architectures, consumers just load the pointer. + */ + atomic_store_ptr((uintptr_t *)&nnode->tn_link_target, + (uintptr_t)symlink); + atomic_store_rel_char((char *)&nnode->tn_link_smr, symlink_smr); break; case VREG: @@ -382,6 +417,7 @@ bool detach) { vm_object_t uobj; + char *symlink; bool last; TMPFS_MP_ASSERT_LOCKED(tmp); @@ -417,7 +453,13 @@ break; case VLNK: - free(node->tn_link, M_TMPFSNAME); + symlink = node->tn_link_target; + atomic_store_ptr((uintptr_t *)&node->tn_link_target, + (uintptr_t)NULL); + if (atomic_load_char(&node->tn_link_smr)) + cache_symlink_free(symlink); + else + free(symlink, M_TMPFSNAME); break; case VREG: Index: sys/fs/tmpfs/tmpfs_vnops.c =================================================================== --- sys/fs/tmpfs/tmpfs_vnops.c +++ sys/fs/tmpfs/tmpfs_vnops.c @@ -1443,13 +1443,39 @@ node = VP_TO_TMPFS_NODE(vp); - error = uiomove(node->tn_link, MIN(node->tn_size, uio->uio_resid), + error = uiomove(node->tn_link_target, MIN(node->tn_size, uio->uio_resid), uio); tmpfs_set_accessed(VFS_TO_TMPFS(vp->v_mount), node); return (error); } +/* + * VOP_FPLOOKUP_SYMLINK routines are subject to special circumstances, see + * the comment above cache_fplookup for details. + * + * Check tmpfs_alloc_node for tmpfs-specific synchronisation notes. + */ +static int +tmpfs_fplookup_symlink(struct vop_fplookup_symlink_args *v) +{ + struct vnode *vp; + struct tmpfs_node *node; + char *symlink; + + vp = v->a_vp; + node = VP_TO_TMPFS_NODE_SMR(vp); + if (__predict_false(node == NULL)) + return (EAGAIN); + if (!atomic_load_char(&node->tn_link_smr)) + return (EAGAIN); + symlink = atomic_load_ptr(&node->tn_link_target); + if (symlink == NULL) + return (EAGAIN); + + return (cache_symlink_resolve(v->a_fpl, symlink, node->tn_size)); +} + static int tmpfs_inactive(struct vop_inactive_args *v) { @@ -1781,6 +1807,7 @@ .vop_open = tmpfs_open, .vop_close = tmpfs_close, .vop_fplookup_vexec = tmpfs_fplookup_vexec, + .vop_fplookup_symlink = tmpfs_fplookup_symlink, .vop_access = tmpfs_access, .vop_stat = tmpfs_stat, .vop_getattr = tmpfs_getattr, Index: sys/kern/vfs_cache.c =================================================================== --- sys/kern/vfs_cache.c +++ sys/kern/vfs_cache.c @@ -548,6 +548,41 @@ static uma_zone_t __read_mostly cache_zone_large; static uma_zone_t __read_mostly cache_zone_large_ts; +char * +cache_symlink_alloc(size_t size, int flags) +{ + + if (size < CACHE_ZONE_SMALL_SIZE) { + return (uma_zalloc_smr(cache_zone_small, flags)); + } + if (size < CACHE_ZONE_LARGE_SIZE) { + return (uma_zalloc_smr(cache_zone_large, flags)); + } + return (NULL); +} + +void +cache_symlink_free(char *string) +{ + size_t size; + + MPASS(string != NULL); + /* + * XXXMJG + */ + size = strlen(string); + + if (size < CACHE_ZONE_SMALL_SIZE) { + uma_zfree_smr(cache_zone_small, string); + return; + } + if (size < CACHE_ZONE_LARGE_SIZE) { + uma_zfree_smr(cache_zone_large, string); + return; + } + __assert_unreachable(); +} + static struct namecache * cache_alloc_uma(int len, bool ts) { @@ -3722,6 +3757,13 @@ #define cache_fpl_handled(x, e) cache_fpl_handled_impl((x), (e), __LINE__) +static bool +cache_fpl_terminated(struct cache_fpl *fpl) +{ + + return (fpl->status != CACHE_FPL_STATUS_UNSET); +} + #define CACHE_FPL_SUPPORTED_CN_FLAGS \ (NC_NOMAKEENTRY | NC_KEEPPOSENTRY | LOCKLEAF | LOCKPARENT | WANTPARENT | \ FOLLOW | LOCKSHARED | SAVENAME | SAVESTART | WILLBEDIR | ISOPEN | \ @@ -3807,13 +3849,6 @@ return (0); } -static bool -cache_fplookup_vnode_supported(struct vnode *vp) -{ - - return (vp->v_type != VLNK); -} - static int __noinline cache_fplookup_negative_promote(struct cache_fpl *fpl, struct namecache *oncp, uint32_t hash) @@ -4010,8 +4045,6 @@ dvp_seqc = fpl->dvp_seqc; tvp = fpl->tvp; - VNPASS(cache_fplookup_vnode_supported(dvp), dvp); - if (cnp->cn_nameiop != LOOKUP) { return (cache_fplookup_final_modifying(fpl)); } @@ -4158,6 +4191,91 @@ return (cache_fpl_handled(fpl, ENOENT)); } +/* + * Resolve a symlink. Called by filesystem-specific routines. + * + * Code flow is: + * ... -> cache_fplookup_symlink -> VOP_FPLOOKUP_SYMLINK -> cache_symlink_resolve + */ +int +cache_symlink_resolve(struct cache_fpl *fpl, const char *string, size_t len) +{ + struct nameidata *ndp; + struct componentname *cnp; + + ndp = fpl->ndp; + cnp = fpl->cnp; + + if (len == 0) { + return (ENOENT); + } + + if (len + ndp->ni_pathlen > MAXPATHLEN) { + return (ENAMETOOLONG); + } + + if (ndp->ni_loopcnt++ >= MAXSYMLINKS) { + return (ELOOP); + } + + if (ndp->ni_pathlen > 1) { + bcopy(ndp->ni_next, cnp->cn_pnbuf + len, ndp->ni_pathlen); + } else { + cnp->cn_pnbuf[len] = '\0'; + } + bcopy(string, cnp->cn_pnbuf, len); + + ndp->ni_pathlen += len; + cnp->cn_nameptr = cnp->cn_pnbuf; + + return (0); +} + +static int __noinline +cache_fplookup_symlink(struct cache_fpl *fpl) +{ + struct nameidata *ndp; + struct componentname *cnp; + struct vnode *dvp, *tvp; + int error; + + ndp = fpl->ndp; + cnp = fpl->cnp; + dvp = fpl->dvp; + tvp = fpl->tvp; + + if (cache_fpl_islastcn(ndp)) { + if ((cnp->cn_flags & FOLLOW) == 0) { + return (cache_fplookup_final(fpl)); + } + } + + error = VOP_FPLOOKUP_SYMLINK(tvp, fpl); + if (__predict_false(error != 0)) { + switch (error) { + case EAGAIN: + return (cache_fpl_partial(fpl)); + case ENOENT: + case ENAMETOOLONG: + case ELOOP: + cache_fpl_smr_exit(fpl); + return (cache_fpl_handled(fpl, error)); + default: + return (cache_fpl_aborted(fpl)); + } + } + + if (*(cnp->cn_nameptr) == '/') { + cache_fpl_handle_root(ndp, &fpl->dvp); + fpl->dvp_seqc = vn_seqc_read_any(tvp); + if (seqc_in_modify(fpl->dvp_seqc)) { + return (cache_fpl_aborted(fpl)); + } + } + + return (0); +} + static int cache_fplookup_next(struct cache_fpl *fpl) { @@ -4207,10 +4325,6 @@ return (cache_fpl_partial(fpl)); } - if (!cache_fplookup_vnode_supported(tvp)) { - return (cache_fpl_partial(fpl)); - } - counter_u64_add(numposhits, 1); SDT_PROBE3(vfs, namecache, lookup, hit, dvp, ncp->nc_name, tvp); return (0); @@ -4519,16 +4633,12 @@ goto out; } - VNPASS(cache_fplookup_vnode_supported(fpl->dvp), fpl->dvp); - for (;;) { error = cache_fplookup_parse(fpl); if (__predict_false(error != 0)) { break; } - VNPASS(cache_fplookup_vnode_supported(fpl->dvp), fpl->dvp); - error = VOP_FPLOOKUP_VEXEC(fpl->dvp, cnp->cn_cred); if (__predict_false(error != 0)) { error = cache_fplookup_failed_vexec(fpl, error); @@ -4558,24 +4668,33 @@ VNPASS(!seqc_in_modify(fpl->tvp_seqc), fpl->tvp); - if (cache_fpl_islastcn(ndp)) { - error = cache_fplookup_final(fpl); - break; - } + if (fpl->tvp->v_type == VLNK) { + error = cache_fplookup_symlink(fpl); + if (cache_fpl_terminated(fpl)) { + break; + } + } else { + if (cache_fpl_islastcn(ndp)) { + error = cache_fplookup_final(fpl); + break; + } - if (!vn_seqc_consistent(fpl->dvp, fpl->dvp_seqc)) { - error = cache_fpl_aborted(fpl); - break; - } + if (!vn_seqc_consistent(fpl->dvp, fpl->dvp_seqc)) { + error = cache_fpl_aborted(fpl); + break; + } - fpl->dvp = fpl->tvp; - fpl->dvp_seqc = fpl->tvp_seqc; + fpl->dvp = fpl->tvp; + fpl->dvp_seqc = fpl->tvp_seqc; + cache_fplookup_parse_advance(fpl); + } - cache_fplookup_parse_advance(fpl); + VNPASS(!seqc_in_modify(fpl->dvp_seqc), fpl->dvp); cache_fpl_checkpoint(fpl, &fpl->snd); } out: switch (fpl->status) { + case CACHE_FPL_STATUS_DESTROYED: case CACHE_FPL_STATUS_UNSET: __assert_unreachable(); break; @@ -4738,10 +4857,9 @@ error = cache_fplookup_impl(dvp, &fpl); out: cache_fpl_smr_assert_not_entered(&fpl); - SDT_PROBE3(vfs, fplookup, lookup, done, ndp, fpl.line, fpl.status); - *status = fpl.status; switch (fpl.status) { + case CACHE_FPL_STATUS_DESTROYED: case CACHE_FPL_STATUS_UNSET: __assert_unreachable(); break; @@ -4757,7 +4875,17 @@ break; case CACHE_FPL_STATUS_ABORTED: cache_fpl_restore_abort(&fpl, &orig); + /* + * Resolving symlinks overwrites data passed by the caller. + * Let namei know. + */ + if (ndp->ni_loopcnt > 0) { + fpl.status = CACHE_FPL_STATUS_DESTROYED; + cache_fpl_cleanup_cnp(cnp); + } break; } + SDT_PROBE3(vfs, fplookup, lookup, done, ndp, fpl.line, fpl.status); + *status = fpl.status; return (error); } Index: sys/kern/vfs_lookup.c =================================================================== --- sys/kern/vfs_lookup.c +++ sys/kern/vfs_lookup.c @@ -567,6 +567,7 @@ ndp->ni_startdir->v_type == VBAD); ndp->ni_lcf = 0; + ndp->ni_loopcnt = 0; ndp->ni_vp = NULL; error = namei_getpath(ndp); @@ -600,8 +601,16 @@ TAILQ_INIT(&ndp->ni_cap_tracker); dp = ndp->ni_startdir; break; + case CACHE_FPL_STATUS_DESTROYED: + ndp->ni_loopcnt = 0; + error = namei_getpath(ndp); + if (__predict_false(error != 0)) { + return (error); + } + /* FALLTHROUGH */ case CACHE_FPL_STATUS_ABORTED: TAILQ_INIT(&ndp->ni_cap_tracker); + MPASS(ndp->ni_lcf == 0); error = namei_setup(ndp, &dp, &pwd); if (error != 0) { namei_cleanup_cnp(cnp); @@ -610,8 +619,6 @@ break; } - ndp->ni_loopcnt = 0; - /* * Locked lookup. */ Index: sys/kern/vfs_subr.c =================================================================== --- sys/kern/vfs_subr.c +++ sys/kern/vfs_subr.c @@ -5446,6 +5446,19 @@ VFS_SMR_ASSERT_ENTERED(); } +void +vop_fplookup_symlink_debugpre(void *ap __unused) +{ + + VFS_SMR_ASSERT_ENTERED(); +} + +void +vop_fplookup_symlink_debugpost(void *ap __unused, int rc __unused) +{ + + VFS_SMR_ASSERT_ENTERED(); +} void vop_strategy_debugpre(void *ap) { Index: sys/kern/vnode_if.src =================================================================== --- sys/kern/vnode_if.src +++ sys/kern/vnode_if.src @@ -156,6 +156,16 @@ }; +%% fplookup_symlink vp - - - +%! fplookup_symlink debugpre vop_fplookup_symlink_debugpre +%! fplookup_symlink debugpost vop_fplookup_symlink_debugpost + +vop_fplookup_symlink { + IN struct vnode *vp; + IN struct cache_fpl *fpl; +}; + + %% access vp L L L vop_access { Index: sys/sys/namei.h =================================================================== --- sys/sys/namei.h +++ sys/sys/namei.h @@ -116,8 +116,8 @@ #ifdef _KERNEL -enum cache_fpl_status { CACHE_FPL_STATUS_ABORTED, CACHE_FPL_STATUS_PARTIAL, - CACHE_FPL_STATUS_HANDLED, CACHE_FPL_STATUS_UNSET }; +enum cache_fpl_status { CACHE_FPL_STATUS_DESTROYED, CACHE_FPL_STATUS_ABORTED, + CACHE_FPL_STATUS_PARTIAL, CACHE_FPL_STATUS_HANDLED, CACHE_FPL_STATUS_UNSET }; int cache_fplookup(struct nameidata *ndp, enum cache_fpl_status *status, struct pwd **pwdp); Index: sys/sys/param.h =================================================================== --- sys/sys/param.h +++ sys/sys/param.h @@ -60,7 +60,7 @@ * in the range 5 to 9. */ #undef __FreeBSD_version -#define __FreeBSD_version 1300131 /* Master, propagated to newvers */ +#define __FreeBSD_version 1300132 /* Master, propagated to newvers */ /* * __FreeBSD_kernel__ indicates that this system uses the kernel of FreeBSD, Index: sys/sys/vnode.h =================================================================== --- sys/sys/vnode.h +++ sys/sys/vnode.h @@ -66,6 +66,7 @@ */ struct namecache; +struct cache_fpl; struct vpollinfo { struct mtx vpi_lock; /* lock to protect below */ @@ -644,6 +645,10 @@ void cache_purge_vgone(struct vnode *vp); void cache_purge_negative(struct vnode *vp); void cache_purgevfs(struct mount *mp); +char *cache_symlink_alloc(size_t size, int flags); +void cache_symlink_free(char *string); +int cache_symlink_resolve(struct cache_fpl *fpl, const char *string, + size_t len); void cache_vop_rename(struct vnode *fdvp, struct vnode *fvp, struct vnode *tdvp, struct vnode *tvp, struct componentname *fcnp, struct componentname *tcnp); void cache_vop_rmdir(struct vnode *dvp, struct vnode *vp); @@ -888,6 +893,8 @@ #ifdef DEBUG_VFS_LOCKS void vop_fplookup_vexec_debugpre(void *a); void vop_fplookup_vexec_debugpost(void *a, int rc); +void vop_fplookup_symlink_debugpre(void *a); +void vop_fplookup_symlink_debugpost(void *a, int rc); void vop_strategy_debugpre(void *a); void vop_lock_debugpre(void *a); void vop_lock_debugpost(void *a, int rc); @@ -898,6 +905,8 @@ #else #define vop_fplookup_vexec_debugpre(x) do { } while (0) #define vop_fplookup_vexec_debugpost(x, y) do { } while (0) +#define vop_fplookup_symlink_debugpre(x) do { } while (0) +#define vop_fplookup_symlink_debugpost(x, y) do { } while (0) #define vop_strategy_debugpre(x) do { } while (0) #define vop_lock_debugpre(x) do { } while (0) #define vop_lock_debugpost(x, y) do { } while (0) Index: sys/ufs/ufs/ufs_vnops.c =================================================================== --- sys/ufs/ufs/ufs_vnops.c +++ sys/ufs/ufs/ufs_vnops.c @@ -2965,6 +2965,7 @@ .vop_accessx = ufs_accessx, .vop_bmap = ufs_bmap, .vop_fplookup_vexec = ufs_fplookup_vexec, + .vop_fplookup_symlink = VOP_EAGAIN, .vop_cachedlookup = ufs_lookup, .vop_close = ufs_close, .vop_create = ufs_create,