OK, dtracing the debug kernel helped here, as certain functions were not inlined:
dtrace -n 'mac*:entry / execname == "nfsd" / {} ' -n 'mac*:return / execname == "nfsd" / { printf("%d %s", arg1, execname); }' -n 'nfs*:entry / execname == "nfsd" / {} ' -n 'nfs*:return / execname == "nfsd" / { printf("%d %s", arg1, execname); }' -n 'vnode*:entry / execname == "nfsd" / {} ' -n 'vnode*:return / execname == "nfsd" / { printf("%d %s", arg1, execname); }' -n 'vn*:entry / execname == "nfsd" / {} ' -n 'vn*:return / execname == "nfsd" / { printf("%d %s", arg1, execname); }' -n 'nfsrv_rephead:entry { nd = arg0 + 0x88; tracemem(nd, 4); printf("rephead %d", arg0);}'
Yeah, the whole thing. Anyway, it produced this hint:
1 269114 mac_vnode_check_open:entry
1 269042 mac_cred_check_enforce:entry
1 269043 mac_cred_check_enforce:return 1 nfsd
1 232364 vn_getpath_ext_with_mntlen:entry
1 231850 build_path_with_parent:return 2 nfsd
1 232365 vn_getpath_ext_with_mntlen:return 2 nfsd
1 232364 vn_getpath_ext_with_mntlen:entry
1 231850 build_path_with_parent:return 2 nfsd
1 232365 vn_getpath_ext_with_mntlen:return 2 nfsd
1 268848 mac_error_select:entry
1 268849 mac_error_select:return 2 nfsd
1 269115 mac_vnode_check_open:return 2 nfsd
ie, it's not that mac_vnode_check_open() but rather that vn_getpath_ext_with_mntlen() fails.
(Technically, build_path_with_parent()).
At the end of my zfs_vnop_create() - if I add the call:
vnode_update_identity(*ap->a_vpp, NULL,
(const char *)ap->a_cnp->cn_nameptr,
ap->a_cnp->cn_namelen, 0,
VNODE_UPDATE_NAME);
I get a successful test run using O_EXCL over nfs, finally.
I am not entirely sure why this is required, only place I call vnode_update_identity() is in vfs_vget().
Otherwise, I've "been getting away" with not calling it all this time. I assume it doesn't hurt to call it.