#pragma ident "@(#)auto_vnops.c 1.70 05/12/19 SMI"
#include <mach/mach_types.h>
#include <mach/machine/boolean.h>
#include <mach/host_priv.h>
#include <mach/host_special_ports.h>
#include <mach/vm_map.h>
#include <vm/vm_map.h>
#include <vm/vm_kern.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/buf.h>
#include <sys/proc.h>
#include <sys/conf.h>
#include <sys/mount.h>
#include <sys/vnode.h>
#include <sys/malloc.h>
#include <sys/dirent.h>
#include <sys/namei.h>
#include <sys/attr.h>
#include <sys/vnode_if.h>
#include <sys/vfs_context.h>
#include <sys/vm.h>
#include <sys/errno.h>
#include <vfs/vfs_support.h>
#include <sys/uio.h>
#include <sys/xattr.h>
#include <sys/syslog.h>
#include <kern/assert.h>
#include <kern/host.h>
#include "autofs.h"
#include "autofs_kern.h"
#include "autofs_protUser.h"
static int auto_getattr(struct vnop_getattr_args *);
static int auto_setattr(struct vnop_setattr_args *);
static int auto_lookup(struct vnop_lookup_args *);
static int auto_readdir(struct vnop_readdir_args *);
static int auto_readlink(struct vnop_readlink_args *);
static int auto_pathconf(struct vnop_pathconf_args *);
static int auto_getxattr(struct vnop_getxattr_args *);
static int auto_setxattr(struct vnop_setxattr_args *);
static int auto_listxattr(struct vnop_listxattr_args *);
static int auto_removexattr(struct vnop_removexattr_args *);
static int auto_inactive(struct vnop_inactive_args *);
static int auto_reclaim(struct vnop_reclaim_args *);
static int auto_kqfilt_add(struct vnop_kqfilt_add_args *);
static int auto_kqfilt_remove(struct vnop_kqfilt_remove_args *);
int (**autofs_vnodeop_p)(void *);
#define VOPFUNC int (*)(void *)
struct vnodeopv_entry_desc autofs_vnodeop_entries[] = {
{&vnop_default_desc, (VOPFUNC)vn_default_error},
{&vnop_lookup_desc, (VOPFUNC)auto_lookup},
{&vnop_open_desc, (VOPFUNC)nop_open},
{&vnop_close_desc, (VOPFUNC)nop_close},
{&vnop_getattr_desc, (VOPFUNC)auto_getattr},
{&vnop_setattr_desc, (VOPFUNC)auto_setattr},
{&vnop_fsync_desc, (VOPFUNC)nop_fsync},
{&vnop_readdir_desc, (VOPFUNC)auto_readdir},
{&vnop_readlink_desc, (VOPFUNC)auto_readlink},
{&vnop_pathconf_desc, (VOPFUNC)auto_pathconf},
{&vnop_setxattr_desc, (VOPFUNC)auto_setxattr},
{&vnop_getxattr_desc, (VOPFUNC)auto_getxattr},
{&vnop_listxattr_desc, (VOPFUNC)auto_listxattr},
{&vnop_removexattr_desc, (VOPFUNC)auto_removexattr},
{&vnop_inactive_desc, (VOPFUNC)auto_inactive},
{&vnop_reclaim_desc, (VOPFUNC)auto_reclaim},
{&vnop_kqfilt_add_desc, (VOPFUNC)auto_kqfilt_add},
{&vnop_kqfilt_remove_desc, (VOPFUNC)auto_kqfilt_remove},
{NULL, NULL}
};
struct vnodeopv_desc autofsfs_vnodeop_opv_desc =
{ &autofs_vnodeop_p, autofs_vnodeop_entries };
extern struct vnodeop_desc vnop_remove_desc;
static uint32_t
auto_bsd_flags(fnnode_t *fnp, vfs_context_t context)
{
vnode_t vp = fntovn(fnp);
fninfo_t *fnip = vfstofni(vnode_mount(vp));
uint32_t flags = 0;
boolean_t istrigger;
int error;
if (fnp->fn_istrigger == FN_TRIGGER_UNKNOWN) {
if (auto_is_automounter(vfs_context_pid(context))) {
return (0);
}
auto_fninfo_lock_shared(fnip, context);
if (vp == fnip->fi_rootvp) {
if (fnip->fi_flags & MF_DIRECT) {
error = auto_check_trigger_request(fnip,
".", 1, &istrigger);
if (error) {
fnp->fn_istrigger = FN_ISNT_TRIGGER;
} else {
if (istrigger)
fnp->fn_istrigger = FN_IS_TRIGGER;
else
fnp->fn_istrigger = FN_ISNT_TRIGGER;
}
} else {
fnp->fn_istrigger = FN_ISNT_TRIGGER;
}
} else {
if (fnip->fi_flags & MF_DIRECT) {
fnp->fn_istrigger = FN_ISNT_TRIGGER;
} else {
error = auto_check_trigger_request(fnip,
fnp->fn_name, fnp->fn_namelen, &istrigger);
if (error) {
fnp->fn_istrigger = FN_ISNT_TRIGGER;
} else {
if (istrigger)
fnp->fn_istrigger = FN_IS_TRIGGER;
else
fnp->fn_istrigger = FN_ISNT_TRIGGER;
}
}
}
auto_fninfo_unlock_shared(fnip, context);
}
switch (fnp->fn_istrigger) {
case FN_TRIGGER_UNKNOWN:
log(LOG_ERR, "Trigger status of %s unknown\n", fnp->fn_name);
flags = 0;
break;
case FN_IS_TRIGGER:
flags = SF_AUTOMOUNT;
break;
case FN_ISNT_TRIGGER:
flags = 0;
break;
}
return (flags);
}
static int
auto_getattr(ap)
struct vnop_getattr_args *ap;
{
vnode_t vp = ap->a_vp;
struct vnode_attr *vap = ap->a_vap;
fnnode_t *fnp = vntofn(vp);
AUTOFS_DPRINT((4, "auto_getattr vp %p\n", (void *)vp));
VATTR_RETURN(vap, va_rdev, 0);
switch (vnode_vtype(vp)) {
case VDIR:
VATTR_RETURN(vap, va_nlink, fnp->fn_linkcnt + 1);
VATTR_RETURN(vap, va_data_size, fnp->fn_direntcnt + 1);
break;
case VLNK:
VATTR_RETURN(vap, va_nlink, fnp->fn_linkcnt);
VATTR_RETURN(vap, va_data_size, fnp->fn_symlinklen);
break;
default:
VATTR_RETURN(vap, va_nlink, fnp->fn_linkcnt);
VATTR_RETURN(vap, va_data_size, 0);
break;
}
VATTR_RETURN(vap, va_total_size, roundup(vap->va_data_size, AUTOFS_BLOCKSIZE));
VATTR_RETURN(vap, va_iosize, AUTOFS_BLOCKSIZE);
VATTR_RETURN(vap, va_uid, fnp->fn_uid);
VATTR_RETURN(vap, va_gid, fnp->fn_gid);
VATTR_RETURN(vap, va_mode, fnp->fn_mode);
if (VATTR_IS_ACTIVE(vap, va_flags)) {
if (vnode_isdir(vp)) {
VATTR_RETURN(vap, va_flags,
auto_bsd_flags(fnp, ap->a_context));
} else {
VATTR_RETURN(vap, va_flags, 0);
}
}
vap->va_access_time.tv_sec = fnp->fn_atime.tv_sec;
vap->va_access_time.tv_nsec = fnp->fn_atime.tv_usec * 1000;
VATTR_SET_SUPPORTED(vap, va_access_time);
vap->va_modify_time.tv_sec = fnp->fn_mtime.tv_sec;
vap->va_modify_time.tv_nsec = fnp->fn_mtime.tv_usec * 1000;
VATTR_SET_SUPPORTED(vap, va_modify_time);
vap->va_change_time.tv_sec = fnp->fn_ctime.tv_sec;
vap->va_change_time.tv_nsec = fnp->fn_ctime.tv_usec * 1000;
VATTR_SET_SUPPORTED(vap, va_change_time);
VATTR_RETURN(vap, va_fileid, fnp->fn_nodeid);
VATTR_RETURN(vap, va_fsid, vfs_statfs(vnode_mount(vp))->f_fsid.val[0]);
VATTR_RETURN(vap, va_filerev, 0);
VATTR_RETURN(vap, va_type, vnode_vtype(vp));
return (0);
}
static int
auto_setattr(ap)
struct vnop_setattr_args *ap;
{
vnode_t vp = ap->a_vp;
struct vnode_attr *vap = ap->a_vap;
fnnode_t *fnp = vntofn(vp);
AUTOFS_DPRINT((4, "auto_setattr vp %p\n", (void *)vp));
VATTR_SET_SUPPORTED(vap, va_uid);
if (VATTR_IS_ACTIVE(vap, va_uid))
fnp->fn_uid = vap->va_uid;
VATTR_SET_SUPPORTED(vap, va_gid);
if (VATTR_IS_ACTIVE(vap, va_gid))
fnp->fn_gid = vap->va_gid;
VATTR_SET_SUPPORTED(vap, va_mode);
if (VATTR_IS_ACTIVE(vap, va_mode))
fnp->fn_mode = vap->va_mode & ALLPERMS;
AUTO_KNOTE(vp, NOTE_ATTRIB);
return (0);
}
static int
map_is_fstab(fnnode_t *dfnp)
{
static const char fstab[] = "-fstab";
struct fninfo *fnip = vfstofni(vnode_mount(fntovn(dfnp)));
if (fnip->fi_maplen == sizeof fstab &&
bcmp(fnip->fi_map, fstab, fnip->fi_maplen) == 0)
return (1);
return (0);
}
static boolean_t
name_is_us(struct componentname *cnp)
{
mach_port_t automount_port;
int error;
kern_return_t ret;
boolean_t is_us;
error = auto_get_automountd_port(&automount_port);
if (error)
return (0);
ret = autofs_check_thishost(automount_port, cnp->cn_nameptr,
cnp->cn_namelen, &is_us);
auto_release_automountd_port(automount_port);
if (ret == KERN_SUCCESS)
return (is_us);
else
return (0);
}
#define OP_NOTHING -1
#define OP_LOOKUP 0
#define OP_MOUNT 1
static int
auto_lookup(ap)
struct vnop_lookup_args *ap;
{
vnode_t dvp = ap->a_dvp;
vnode_t *vpp = ap->a_vpp;
vnode_t vp;
struct componentname *cnp = ap->a_cnp;
u_long nameiop = cnp->cn_nameiop;
u_long flags = cnp->cn_flags;
int namelen = cnp->cn_namelen;
vfs_context_t context = ap->a_context;
ucred_t cred = vfs_context_ucred(context);
int error = 0;
fninfo_t *dfnip;
fnnode_t *dfnp = NULL;
fnnode_t *fnp = NULL;
char *searchnm;
int searchnmlen;
int operation;
dfnip = vfstofni(vnode_mount(dvp));
AUTOFS_DPRINT((3, "auto_lookup: dvp=%p (%s) name=%.*s\n",
(void *)dvp, dfnip->fi_map, namelen, cnp->cn_nameptr));
if (!vnode_isdir(dvp))
return (ENOTDIR);
if (namelen == 0) {
error = vnode_get(dvp);
if (error)
return (error);
*vpp = dvp;
return (0);
}
if (cnp->cn_nameptr[0] == '.') {
if (namelen == 1) {
if ((nameiop == RENAME) && (flags & WANTPARENT) &&
(flags & ISLASTCN))
return (EISDIR);
error = vnode_get(dvp);
if (error)
return (error);
*vpp = dvp;
return (0);
} else if ((namelen == 2) && (cnp->cn_nameptr[1] == '.')) {
fnnode_t *pdfnp;
pdfnp = (vntofn(dvp))->fn_parent;
assert(pdfnp != NULL);
#if 0
if (pdfnp == pdfnp->fn_globals->fng_rootfnnodep) {
vnode_t vp;
vfs_lock_wait(vnode_mount(dvp));
if (vnode_mount(dvp)->vfs_flag & VFS_UNMOUNTED) {
vfs_unlock(vnode_mount(dvp));
return (EIO);
}
vp = vnode_mount(dvp)->mnt_vnodecovered;
error = vnode_get(vp);
vfs_unlock(vnode_mount(dvp));
error = VNOP_LOOKUP(vp, nm, vpp, pnp, flags, rdir, cred);
vnode_put(vp);
return (error);
} else {
#else
{
#endif
*vpp = fntovn(pdfnp);
return (vnode_get(*vpp));
}
}
}
top:
dfnp = vntofn(dvp);
searchnm = cnp->cn_nameptr;
searchnmlen = namelen;
operation = OP_NOTHING;
AUTOFS_DPRINT((3, "auto_lookup: dvp=%p dfnp=%p\n", (void *)dvp,
(void *)dfnp));
lck_mtx_lock(dfnp->fn_lock);
if (dfnp->fn_flags & (MF_LOOKUP | MF_INPROG)) {
lck_mtx_unlock(dfnp->fn_lock);
error = auto_wait4mount(dfnp, context);
if (error == EAGAIN)
goto top;
if (error)
return (error);
} else
lck_mtx_unlock(dfnp->fn_lock);
auto_fninfo_lock_shared(dfnip, context);
lck_rw_lock_shared(dfnp->fn_rwlock);
error = auto_search(dfnp, cnp->cn_nameptr, cnp->cn_namelen, &fnp, cred);
if (error) {
if (dvp == dfnip->fi_rootvp) {
if (dfnip->fi_flags & MF_DIRECT) {
error = ENOENT;
} else {
if (!lck_rw_lock_shared_to_exclusive(dfnp->fn_rwlock)) {
lck_rw_lock_exclusive(dfnp->fn_rwlock);
error = auto_search(dfnp,
cnp->cn_nameptr,
cnp->cn_namelen, &fnp, cred);
}
assert(lck_rw_held_exclusive(dfnp->fn_rwlock));
if (error) {
if (map_is_fstab(dfnp) &&
name_is_us(cnp)) {
error = auto_enter(dfnp, cnp,
NULL, &fnp, "/", NULL);
} else {
error = auto_enter(dfnp, cnp,
NULL, &fnp, NULL, NULL);
if (!error)
operation = OP_MOUNT;
}
}
}
} else if ((dfnp->fn_dirents == NULL) &&
!vnode_isvroot(dvp) &&
vnode_isvroot(fntovn(dfnp->fn_parent))) {
operation = OP_MOUNT;
error = vnode_get(fntovn(dfnp));
fnp = dfnp;
searchnm = dfnp->fn_name;
searchnmlen = dfnp->fn_namelen;
}
}
if (error == EAGAIN) {
auto_fninfo_unlock_shared(dfnip, context);
lck_rw_done(dfnp->fn_rwlock);
goto top;
}
if (error) {
auto_fninfo_unlock_shared(dfnip, context);
lck_rw_done(dfnp->fn_rwlock);
return (error);
}
lck_mtx_lock(fnp->fn_lock);
lck_rw_done(dfnp->fn_rwlock);
if (operation == OP_MOUNT) {
if ((flags & ISLASTCN) &&
(nameiop != LOOKUP || (flags & NOTRIGGER)))
operation = OP_LOOKUP;
}
if ((fnp->fn_flags & MF_LOOKUP) ||
((operation == OP_MOUNT) && (fnp->fn_flags & MF_INPROG))) {
auto_fninfo_unlock_shared(dfnip, context);
lck_mtx_unlock(fnp->fn_lock);
error = auto_wait4mount(fnp, context);
vnode_put(fntovn(fnp));
if (error && error != EAGAIN)
return (error);
goto top;
}
if (operation == OP_NOTHING) {
error = fnp->fn_error;
if ((error == EINTR) || (error == EAGAIN)) {
operation = OP_LOOKUP;
} else {
auto_fninfo_unlock_shared(dfnip, context);
lck_mtx_unlock(fnp->fn_lock);
if (!error)
*vpp = fntovn(fnp);
else
vnode_put(fntovn(fnp));
return (error);
}
}
switch (operation) {
case OP_LOOKUP:
AUTOFS_BLOCK_OTHERS(fnp, MF_LOOKUP);
fnp->fn_error = 0;
lck_mtx_unlock(fnp->fn_lock);
error = auto_lookup_aux(fnp, searchnm, searchnmlen, context);
auto_fninfo_unlock_shared(dfnip, context);
vp = fntovn(fnp);
if (!error) {
*vpp = vp;
} else {
vnode_put(vp);
}
break;
case OP_MOUNT:
AUTOFS_BLOCK_OTHERS(fnp, MF_INPROG);
fnp->fn_error = 0;
lck_mtx_unlock(fnp->fn_lock);
auto_new_mount_thread(fnp, searchnm, searchnmlen, context);
error = auto_wait4mount(fnp, context);
auto_fninfo_unlock_shared(dfnip, context);
vp = fntovn(fnp);
if (!error) {
*vpp = vp;
} else {
vnode_put(vp);
}
break;
default:
auto_fninfo_unlock_shared(dfnip, context);
log(LOG_WARNING, "auto_lookup: unknown operation %d\n",
operation);
}
AUTOFS_DPRINT((5, "auto_lookup: name=%s *vpp=%p return=%d\n",
cnp->cn_nameptr, (void *)*vpp, error));
return (error);
}
static int autofs_nobrowse = 0;
#define MAXDIRBUFSIZE 65536
int
auto_readdir(ap)
struct vnop_readdir_args *ap;
{
vnode_t vp = ap->a_vp;
struct uio *uiop = ap->a_uio;
int status;
uint64_t return_offset;
boolean_t return_eof;
byte_buffer return_buffer;
mach_msg_type_number_t return_bufcount;
vm_map_offset_t map_data;
vm_offset_t data;
fnnode_t *fnp = vntofn(vp);
fnnode_t *cfnp, *nfnp;
struct dirent *dp;
off_t offset;
uint64_t outcount = 0, count;
void *outbuf;
user_ssize_t alloc_count;
fninfo_t *fnip = vfstofni(vnode_mount(vp));
kern_return_t ret;
int error = 0;
int reached_max = 0;
int myeof = 0;
int this_reclen;
mach_port_t automount_port;
AUTOFS_DPRINT((4, "auto_readdir vp=%p offset=%lld\n",
(void *)vp, uio_offset(uiop)));
if (ap->a_numdirent != NULL)
*ap->a_numdirent = 0;
if (ap->a_flags & (VNODE_READDIR_EXTENDED | VNODE_READDIR_REQSEEKOFF))
return (EINVAL);
if (ap->a_eofflag != NULL)
*ap->a_eofflag = 0;
alloc_count = uio_resid(uiop);
if (alloc_count < DIRENT_RECLEN(1))
return (EINVAL);
if (alloc_count > MAXDIRBUFSIZE)
alloc_count = MAXDIRBUFSIZE;
auto_fninfo_lock_shared(fnip, ap->a_context);
lck_rw_lock_shared(fnp->fn_rwlock);
if (uio_offset(uiop) >= AUTOFS_DAEMONCOOKIE) {
if (fnip->fi_flags & MF_DIRECT) {
myeof = 1;
if (ap->a_eofflag != NULL)
*ap->a_eofflag = 1;
goto done;
}
again:
lck_rw_unlock_shared(fnp->fn_rwlock);
count = 0;
error = auto_get_automountd_port(&automount_port);
if (error)
goto done_nolock;
ret = autofs_readdir(automount_port, fnip->fi_map,
uio_offset(uiop), alloc_count, &status,
&return_offset, &return_eof, &return_buffer,
&return_bufcount);
auto_release_automountd_port(automount_port);
lck_rw_lock_shared(fnp->fn_rwlock);
if (ret == KERN_SUCCESS)
error = status;
else {
error = EIO;
}
if (error)
goto done;
ret = vm_map_copyout(kernel_map, &map_data,
(vm_map_copy_t)return_buffer);
if (ret != KERN_SUCCESS) {
error = EIO;
goto done;
}
data = CAST_DOWN(vm_offset_t, map_data);
if (return_bufcount != 0) {
struct dirent *odp;
struct dirent *cdp;
dp = (struct dirent *)data;
odp = dp;
cdp = dp;
do {
this_reclen = RECLEN(cdp);
if (auto_search(fnp, cdp->d_name, cdp->d_namlen,
NULL, vfs_context_ucred(ap->a_context))) {
if (cdp != odp)
bcopy(cdp, odp,
(size_t)this_reclen);
odp = nextdp(odp);
outcount += this_reclen;
if (ap->a_numdirent)
++(*ap->a_numdirent);
} else {
if (odp == dp) {
dp = nextdp(dp);
odp = dp;
}
}
count += this_reclen;
cdp = (struct dirent *)
((char *)cdp + this_reclen);
} while (count < return_bufcount);
if (outcount)
error = uiomove((caddr_t)dp, outcount, uiop);
uio_setoffset(uiop, return_offset);
} else {
if (return_eof == 0) {
error = EINVAL;
}
}
vm_deallocate(kernel_map, data, return_bufcount);
if (return_eof && !error) {
myeof = 1;
if (ap->a_eofflag != NULL)
*ap->a_eofflag = 1;
}
if (!error && !myeof && outcount == 0) {
goto again;
}
goto done;
}
MALLOC(outbuf, void *, alloc_count, M_AUTOFS, M_WAITOK);
dp = outbuf;
if (uio_offset(uiop) == 0) {
this_reclen = DIRENT_RECLEN(1);
if (alloc_count < this_reclen) {
error = EINVAL;
goto done;
}
dp->d_ino = (ino_t)fnp->fn_nodeid;
dp->d_reclen = (uint16_t)this_reclen;
#if 0
dp->d_type = DT_DIR;
#else
dp->d_type = DT_UNKNOWN;
#endif
dp->d_namlen = 1;
(void) strncpy(dp->d_name, ".",
DIRENT_NAMELEN(this_reclen));
outcount += dp->d_reclen;
dp = nextdp(dp);
if (ap->a_numdirent)
++(*ap->a_numdirent);
this_reclen = DIRENT_RECLEN(2);
if (alloc_count < outcount + this_reclen) {
error = EINVAL;
FREE(outbuf, M_AUTOFS);
goto done;
}
dp->d_reclen = (uint16_t)this_reclen;
dp->d_ino = (ino_t)fnp->fn_parent->fn_nodeid;
#if 0
dp->d_type = DT_DIR;
#else
dp->d_type = DT_UNKNOWN;
#endif
dp->d_namlen = 2;
(void) strncpy(dp->d_name, "..",
DIRENT_NAMELEN(this_reclen));
outcount += dp->d_reclen;
dp = nextdp(dp);
if (ap->a_numdirent)
++(*ap->a_numdirent);
}
offset = 2;
cfnp = fnp->fn_dirents;
while (cfnp != NULL) {
nfnp = cfnp->fn_next;
offset = cfnp->fn_offset;
if ((offset >= uio_offset(uiop)) &&
(!(cfnp->fn_flags & MF_LOOKUP))) {
int reclen;
reclen = (int)DIRENT_RECLEN(cfnp->fn_namelen);
if (outcount + reclen > alloc_count) {
reached_max = 1;
break;
}
dp->d_reclen = (uint16_t)reclen;
dp->d_ino = (ino_t)cfnp->fn_nodeid;
#if 0
dp->d_type = vnode_isdir(cfnp->fn_vnode) ? DT_DIR : DT_LNK;
#else
dp->d_type = DT_UNKNOWN;
#endif
dp->d_namlen = cfnp->fn_namelen;
(void) strncpy(dp->d_name, cfnp->fn_name,
DIRENT_NAMELEN(reclen));
outcount += dp->d_reclen;
dp = nextdp(dp);
if (ap->a_numdirent)
++(*ap->a_numdirent);
}
cfnp = nfnp;
}
if (outcount)
error = uiomove(outbuf, outcount, uiop);
if (!error) {
if (reached_max) {
uio_setoffset(uiop, offset);
if (outcount == 0)
error = EINVAL;
} else if (autofs_nobrowse ||
(fnip->fi_mntflags & AUTOFS_MNT_NORDDIR) ||
(fnp->fn_trigger != NULL) ||
(!vnode_isvroot(vp) &&
vnode_isvroot(fntovn(fnp->fn_parent)) &&
(fnp->fn_dirents == NULL))) {
uio_setoffset(uiop, offset + 1);
if (ap->a_eofflag != NULL)
*ap->a_eofflag = 1;
} else {
uio_setoffset(uiop, AUTOFS_DAEMONCOOKIE);
}
}
FREE(outbuf, M_AUTOFS);
done:
lck_rw_unlock_shared(fnp->fn_rwlock);
done_nolock:
auto_fninfo_unlock_shared(fnip, ap->a_context);
AUTOFS_DPRINT((5, "auto_readdir vp=%p offset=%lld eof=%d\n",
(void *)vp, uio_offset(uiop), myeof));
return (error);
}
static int
auto_readlink(ap)
struct vnop_readlink_args *ap;
{
vnode_t vp = ap->a_vp;
uio_t uiop = ap->a_uio;
fnnode_t *fnp = vntofn(vp);
int error;
struct timeval now;
AUTOFS_DPRINT((4, "auto_readlink: vp=%p\n", (void *)vp));
microtime(&now);
fnp->fn_ref_time = now.tv_sec;
if (!vnode_islnk(vp))
error = EINVAL;
else {
assert(!(fnp->fn_flags & (MF_INPROG | MF_LOOKUP)));
fnp->fn_atime = now;
error = uiomove(fnp->fn_symlink, MIN(fnp->fn_symlinklen,
uio_resid(uiop)), uiop);
}
AUTOFS_DPRINT((5, "auto_readlink: error=%d\n", error));
return (error);
}
static int
auto_pathconf(ap)
struct vnop_pathconf_args *ap;
{
switch (ap->a_name) {
case _PC_LINK_MAX:
*ap->a_retval = 32767;
break;
case _PC_NAME_MAX:
*ap->a_retval = NAME_MAX;
break;
case _PC_PATH_MAX:
*ap->a_retval = PATH_MAX;
break;
case _PC_CHOWN_RESTRICTED:
*ap->a_retval = 200112;
break;
case _PC_NO_TRUNC:
*ap->a_retval = 0;
break;
case _PC_CASE_SENSITIVE:
*ap->a_retval = 1;
break;
case _PC_CASE_PRESERVING:
*ap->a_retval = 1;
break;
default:
return (EINVAL);
}
return (0);
}
static int
auto_getxattr(ap)
struct vnop_getxattr_args *ap;
{
vnode_t vp = ap->a_vp;
fnnode_t *fnp = vntofn(vp);
struct uio *uio = ap->a_uio;
size_t attrsize;
int retval = 0;
if (uio_offset(uio) != 0) {
retval = EINVAL;
goto out;
}
if (strncmp(ap->a_name, XATTR_CATEGORYNAME, XATTR_CATEGORYNAMELEN) == 0) {
lck_rw_lock_shared(fnp->fn_rwlock);
if (!(fnp->xattr_data.xattr_category)) {
retval = ENOATTR;
goto out_unlock;
}
*ap->a_size = XATTR_CATEGORY_MAXSIZE;
if (uio == NULL) {
goto out_unlock;
}
attrsize = uio_resid(uio);
if (attrsize < XATTR_CATEGORY_MAXSIZE) {
retval = ERANGE;
goto out_unlock;
}
retval = uiomove((caddr_t)&(fnp->xattr_data.xattr_category), attrsize, uio);
} else {
retval = ENOATTR;
goto out;
}
out_unlock:
lck_rw_unlock_shared(fnp->fn_rwlock);
out:
return (retval);
}
static int
auto_setxattr(ap)
struct vnop_setxattr_args *ap;
{
vnode_t vp = ap->a_vp;
fnnode_t *fnp = vntofn(vp);
struct uio *uio = ap->a_uio;
struct autofs_xattr_data new_xattr_data;
size_t attrsize;
int retval = 0;
if (strncmp(ap->a_name, XATTR_CATEGORYNAME, XATTR_CATEGORYNAMELEN) == 0) {
if (uio_offset(uio) != 0) {
retval = EINVAL;
goto out;
}
attrsize = uio_resid(uio);
if (attrsize != XATTR_CATEGORY_MAXSIZE) {
retval = EINVAL;
goto out;
}
lck_rw_lock_exclusive(fnp->fn_rwlock);
if ((ap->a_options & XATTR_CREATE) && fnp->xattr_data.xattr_category) {
retval = EEXIST;
goto out_unlock;
}
if ((ap->a_options & XATTR_REPLACE) && !(fnp->xattr_data.xattr_category)) {
retval = ENOATTR;
goto out_unlock;
}
retval = uiomove((caddr_t) &new_xattr_data.xattr_category, attrsize, uio);
if (retval) {
goto out_unlock;
}
fnp->xattr_data.xattr_category = new_xattr_data.xattr_category;
AUTO_KNOTE(vp, NOTE_ATTRIB);
} else {
retval = EPERM;
goto out;
}
out_unlock:
lck_rw_unlock_exclusive(fnp->fn_rwlock);
out:
return (retval);
}
static int
auto_removexattr(ap)
struct vnop_removexattr_args *ap;
{
struct vnode *vp = ap->a_vp;
fnnode_t *fnp = vntofn(vp);
int retval = 0;
if (strncmp(ap->a_name, XATTR_CATEGORYNAME, XATTR_CATEGORYNAMELEN) == 0) {
lck_rw_lock_shared(fnp->fn_rwlock);
if (!(fnp->xattr_data.xattr_category)) {
retval = ENOATTR;
goto out_unlock;
}
fnp->xattr_data.xattr_category = 0;
AUTO_KNOTE(vp, NOTE_ATTRIB);
} else {
retval = ENOATTR;
goto out;
}
out_unlock:
lck_rw_unlock_shared(fnp->fn_rwlock);
out:
return (retval);
}
static int
auto_listxattr(ap)
struct vnop_listxattr_args *ap;
{
struct vnode *vp = ap->a_vp;
fnnode_t *fnp = vntofn(vp);
struct uio *uio = ap->a_uio;
int retval = 0;
*ap->a_size = 0;
if (!fnp->xattr_data.xattr_category) {
retval = 0;
goto out;
}
lck_rw_lock_shared(fnp->fn_rwlock);
if (uio == NULL) {
*ap->a_size += XATTR_CATEGORYNAMELEN;
} else if (uio_resid(uio) < XATTR_CATEGORYNAMELEN) {
retval = ERANGE;
goto out_unlock;
} else {
retval = uiomove((caddr_t) XATTR_CATEGORYNAME, XATTR_CATEGORYNAMELEN, uio);
if (retval) {
retval = ERANGE;
goto out_unlock;
}
}
out_unlock:
lck_rw_unlock_shared(fnp->fn_rwlock);
out:
return (retval);
}
static int
auto_inactive(ap)
struct vnop_inactive_args *ap;
{
vnode_t vp = ap->a_vp;
fnnode_t *fnp = vntofn(vp);
fnnode_t *dfnp = fnp->fn_parent;
AUTOFS_DPRINT((4, "auto_inactive: vp=%p fn_link=%d\n",
(void *)vp, fnp->fn_linkcnt));
assert(dfnp != NULL);
#if 0
assert(rw_owner(&dfnp->fn_rwlock) != current_thread());
#endif
lck_rw_lock_exclusive(dfnp->fn_rwlock);
if (fnp->fn_linkcnt == 1) {
auto_disconnect(dfnp, fnp);
lck_rw_unlock_exclusive(dfnp->fn_rwlock);
vnode_recycle(vp);
AUTOFS_DPRINT((5, "auto_inactive: (exit) vp=%p freed\n",
(void *)vp));
return (0);
} else if (fnp->fn_linkcnt == 0) {
fnp->fn_parent = NULL;
}
lck_rw_unlock_exclusive(dfnp->fn_rwlock);
AUTOFS_DPRINT((5, "auto_inactive: (exit) vp=%p fn_link=%d\n",
(void *)vp, fnp->fn_linkcnt));
return (0);
}
static int
auto_reclaim(ap)
struct vnop_reclaim_args *ap;
{
vnode_t vp = ap->a_vp;
fnnode_t *fnp = vntofn(vp);
AUTOFS_DPRINT((4, "auto_reclaim: vp=%p fn_link=%d\n",
(void *)vp, fnp->fn_linkcnt));
auto_freefnnode(fnp);
vnode_clearfsnode(vp);
AUTOFS_DPRINT((5, "auto_reclaim: (exit) vp=%p freed\n",
(void *)vp));
return (0);
}
static void
filt_autofsdetach(struct knote *kn)
{
vnode_t vp = (vnode_t)kn->kn_hook;
fnnode_t *fnp = vntofn(vp);
if (vnode_getwithvid(vp, kn->kn_hookid))
return;
if (1) {
lck_mtx_lock(fnp->fn_lock);
(void) KNOTE_DETACH(&fnp->fn_knotes, kn);
lck_mtx_unlock(fnp->fn_lock);
}
vnode_put(vp);
}
static int
filt_autofsvnode(struct knote *kn, long hint)
{
vnode_t vp = (vnode_t)kn->kn_hook;
if (hint == 0) {
if ((vnode_getwithvid(vp, kn->kn_hookid) != 0)) {
hint = NOTE_REVOKE;
} else
vnode_put(vp);
}
if (kn->kn_sfflags & hint)
kn->kn_fflags |= hint;
if (hint == NOTE_REVOKE) {
kn->kn_flags |= (EV_EOF | EV_ONESHOT);
return (1);
}
return (kn->kn_fflags != 0);
}
static struct filterops autofsvnode_filtops =
{ 1, NULL, filt_autofsdetach, filt_autofsvnode };
static int
auto_kqfilt_add(ap)
struct vnop_kqfilt_add_args *ap;
{
vnode_t vp = ap->a_vp;
struct knote *kn = ap->a_kn;
fnnode_t *fnp = vntofn(vp);
switch (kn->kn_filter) {
case EVFILT_VNODE:
kn->kn_fop = &autofsvnode_filtops;
break;
default:
return (EINVAL);
}
kn->kn_hook = (caddr_t)vp;
kn->kn_hookid = vnode_vid(vp);
lck_mtx_lock(fnp->fn_lock);
KNOTE_ATTACH(&fnp->fn_knotes, kn);
lck_mtx_unlock(fnp->fn_lock);
return (0);
}
static int
auto_kqfilt_remove(ap)
struct vnop_kqfilt_remove_args __unused *ap;
{
int result;
result = ENOTSUP;
return (result);
}