#include <sys/param.h>
#include <sys/systm.h>
#include <sys/thread.h>
#include <sys/t_lock.h>
#include <sys/time.h>
#include <sys/vnode.h>
#include <sys/vfs.h>
#include <sys/errno.h>
#include <sys/buf.h>
#include <sys/stat.h>
#include <sys/cred.h>
#include <sys/kmem.h>
#include <sys/debug.h>
#include <sys/vmsystm.h>
#include <sys/flock.h>
#include <sys/share.h>
#include <sys/cmn_err.h>
#include <sys/tiuser.h>
#include <sys/sysmacros.h>
#include <sys/callb.h>
#include <sys/acl.h>
#include <sys/kstat.h>
#include <sys/signal.h>
#include <sys/list.h>
#include <sys/zone.h>
#include <netsmb/smb.h>
#include <netsmb/smb_conn.h>
#include <netsmb/smb_subr.h>
#include <smbfs/smbfs.h>
#include <smbfs/smbfs_node.h>
#include <smbfs/smbfs_subr.h>
#ifdef _KERNEL
#include <vm/hat.h>
#include <vm/as.h>
#include <vm/page.h>
#include <vm/pvn.h>
#include <vm/seg.h>
#include <vm/seg_map.h>
#include <vm/seg_vn.h>
#endif
#define ATTRCACHE_VALID(vp) (gethrtime() < VTOSMB(vp)->r_attrtime)
static int smbfs_getattr_cache(vnode_t *, smbfattr_t *);
static void smbfattr_to_vattr(vnode_t *, smbfattr_t *, vattr_t *);
static void smbfattr_to_xvattr(smbfattr_t *, vattr_t *);
static int smbfs_getattr_otw(vnode_t *, struct smbfattr *, cred_t *);
struct smi_globals {
kmutex_t smg_lock;
list_t smg_list;
boolean_t smg_destructor_called;
};
typedef struct smi_globals smi_globals_t;
static zone_key_t smi_list_key;
int
smbfs_waitfor_purge_complete(vnode_t *vp)
{
smbnode_t *np;
k_sigset_t smask;
np = VTOSMB(vp);
if (np->r_serial != NULL && np->r_serial != curthread) {
mutex_enter(&np->r_statelock);
sigintr(&smask, VTOSMI(vp)->smi_flags & SMI_INT);
while (np->r_serial != NULL) {
if (!cv_wait_sig(&np->r_cv, &np->r_statelock)) {
sigunintr(&smask);
mutex_exit(&np->r_statelock);
return (EINTR);
}
}
sigunintr(&smask);
mutex_exit(&np->r_statelock);
}
return (0);
}
int
smbfs_validate_caches(
struct vnode *vp,
cred_t *cr)
{
struct smbfattr fa;
int error;
if (ATTRCACHE_VALID(vp)) {
error = smbfs_waitfor_purge_complete(vp);
if (error)
return (error);
return (0);
}
return (smbfs_getattr_otw(vp, &fa, cr));
}
void
smbfs_purge_caches(struct vnode *vp, cred_t *cr)
{
if (vn_has_cached_data(vp)) {
(void) VOP_PUTPAGE(vp, (u_offset_t)0, 0, B_INVAL, cr, NULL);
}
}
static void
smbfs_cache_check(
struct vnode *vp,
struct smbfattr *fap,
cred_t *cr)
{
smbnode_t *np;
int purge_data = 0;
int purge_acl = 0;
np = VTOSMB(vp);
mutex_enter(&np->r_statelock);
if (np->r_attr.fa_mtime.tv_sec != fap->fa_mtime.tv_sec ||
np->r_attr.fa_mtime.tv_nsec != fap->fa_mtime.tv_nsec)
purge_data = 1;
if (np->r_attr.fa_size != fap->fa_size)
purge_data = 1;
if (np->r_attr.fa_ctime.tv_sec != fap->fa_ctime.tv_sec ||
np->r_attr.fa_ctime.tv_nsec != fap->fa_ctime.tv_nsec)
purge_acl = 1;
if (purge_acl) {
np->r_sectime = gethrtime();
}
mutex_exit(&np->r_statelock);
if (purge_data)
smbfs_purge_caches(vp, cr);
}
void
smbfs_attrcache_fa(vnode_t *vp, struct smbfattr *fap)
{
smbnode_t *np;
smbmntinfo_t *smi;
hrtime_t delta, now;
u_offset_t newsize;
vtype_t vtype, oldvt;
mode_t mode;
np = VTOSMB(vp);
smi = VTOSMI(vp);
if (fap->fa_attr & SMB_FA_DIR) {
vtype = VDIR;
mode = smi->smi_dmode;
} else {
vtype = VREG;
mode = smi->smi_fmode;
}
mutex_enter(&np->r_statelock);
now = gethrtime();
if (fap->fa_mtime.tv_sec != np->r_attr.fa_mtime.tv_sec ||
fap->fa_mtime.tv_nsec != np->r_attr.fa_mtime.tv_nsec ||
fap->fa_size != np->r_attr.fa_size)
np->r_mtime = now;
if ((smi->smi_flags & SMI_NOAC) || (vp->v_flag & VNOCACHE))
delta = 0;
else {
delta = now - np->r_mtime;
if (vtype == VDIR) {
if (delta < smi->smi_acdirmin)
delta = smi->smi_acdirmin;
else if (delta > smi->smi_acdirmax)
delta = smi->smi_acdirmax;
} else {
if (delta < smi->smi_acregmin)
delta = smi->smi_acregmin;
else if (delta > smi->smi_acregmax)
delta = smi->smi_acregmax;
}
}
np->r_attrtime = now + delta;
np->r_attr = *fap;
np->n_mode = mode;
oldvt = vp->v_type;
vp->v_type = vtype;
newsize = fap->fa_size;
if (vtype == VDIR && newsize < DEV_BSIZE)
newsize = DEV_BSIZE;
if (np->r_size != newsize &&
(!vn_has_cached_data(vp) ||
(!(np->r_flags & RDIRTY) && np->r_count == 0))) {
np->r_size = newsize;
}
np->n_flag &= ~NATTRCHANGED;
mutex_exit(&np->r_statelock);
if (oldvt != vtype) {
SMBVDEBUG("vtype change %d to %d\n", oldvt, vtype);
}
}
int
smbfs_getattr_cache(vnode_t *vp, struct smbfattr *fap)
{
smbnode_t *np;
int error;
np = VTOSMB(vp);
mutex_enter(&np->r_statelock);
if (gethrtime() >= np->r_attrtime) {
error = ENOENT;
} else {
*fap = np->r_attr;
error = 0;
}
mutex_exit(&np->r_statelock);
return (error);
}
static int
smbfs_getattr_otw(vnode_t *vp, struct smbfattr *fap, cred_t *cr)
{
struct smb_cred scred;
smbnode_t *np = VTOSMB(vp);
smb_share_t *ssp = np->n_mount->smi_share;
smb_fh_t *fhp = NULL;
int error;
bzero(fap, sizeof (*fap));
if (vp->v_flag & V_XATTRDIR) {
fap->fa_attr = SMB_FA_DIR;
fap->fa_size = DEV_BSIZE;
return (0);
}
if (smbfs_rw_enter_sig(&np->r_lkserlock, RW_READER, SMBINTR(vp)))
return (EINTR);
smb_credinit(&scred, cr);
#if 0
if (np->n_flag & N_XATTR) {
error = smbfs_xa_getfattr(np, fap, scrp);
goto out;
}
#endif
if (np->n_fidrefs > 0 &&
(fhp = np->n_fid) != NULL &&
(fhp->fh_vcgenid == ssp->ss_vcgenid)) {
error = smbfs_smb_getfattr(np, fhp, fap, &scred);
} else {
error = smbfs_smb_getpattr(np, fap, &scred);
}
smb_credrele(&scred);
smbfs_rw_exit(&np->r_lkserlock);
if (error) {
smbfs_attrcache_remove(np);
if (error == ENOENT || error == ENOTDIR) {
smbfs_attrcache_prune(np);
}
return (error);
}
smbfs_cache_check(vp, fap, cr);
smbfs_attrcache_fa(vp, fap);
return (0);
}
int
smbfsgetattr(vnode_t *vp, struct vattr *vap, cred_t *cr)
{
struct smbfattr fa;
smbmntinfo_t *smi;
uint_t mask;
int error;
smi = VTOSMI(vp);
ASSERT(curproc->p_zone == smi->smi_zone_ref.zref_zone);
mask = AT_ALL;
if (vap->va_mask & (AT_UID | AT_GID)) {
if (smi->smi_flags & SMI_ACL)
(void) smbfs_acl_getids(vp, cr);
} else {
mask &= ~(AT_UID | AT_GID);
}
error = smbfs_getattr_cache(vp, &fa);
if (error)
error = smbfs_getattr_otw(vp, &fa, cr);
if (error)
return (error);
vap->va_mask |= mask;
smbfattr_to_vattr(vp, &fa, vap);
if (vap->va_mask & AT_XVATTR)
smbfattr_to_xvattr(&fa, vap);
return (0);
}
void
smbfattr_to_vattr(vnode_t *vp, struct smbfattr *fa, struct vattr *vap)
{
struct smbnode *np = VTOSMB(vp);
vap->va_type = vp->v_type;
vap->va_mode = np->n_mode;
vap->va_uid = np->n_uid;
vap->va_gid = np->n_gid;
vap->va_fsid = vp->v_vfsp->vfs_dev;
vap->va_nodeid = np->n_ino;
vap->va_nlink = 1;
vap->va_size = np->r_size;
vap->va_atime = fa->fa_atime;
vap->va_mtime = fa->fa_mtime;
vap->va_ctime = fa->fa_ctime;
vap->va_rdev = vp->v_rdev;
vap->va_blksize = MAXBSIZE;
vap->va_nblocks = (fsblkcnt64_t)btod(np->r_attr.fa_allocsz);
vap->va_seq = 0;
}
static void
smbfattr_to_xvattr(struct smbfattr *fa, struct vattr *vap)
{
xvattr_t *xvap = (xvattr_t *)vap;
xoptattr_t *xoap = NULL;
if ((xoap = xva_getxoptattr(xvap)) == NULL)
return;
if (XVA_ISSET_REQ(xvap, XAT_CREATETIME)) {
xoap->xoa_createtime = fa->fa_createtime;
XVA_SET_RTN(xvap, XAT_CREATETIME);
}
if (XVA_ISSET_REQ(xvap, XAT_ARCHIVE)) {
xoap->xoa_archive =
((fa->fa_attr & SMB_FA_ARCHIVE) != 0);
XVA_SET_RTN(xvap, XAT_ARCHIVE);
}
if (XVA_ISSET_REQ(xvap, XAT_SYSTEM)) {
xoap->xoa_system =
((fa->fa_attr & SMB_FA_SYSTEM) != 0);
XVA_SET_RTN(xvap, XAT_SYSTEM);
}
if (XVA_ISSET_REQ(xvap, XAT_READONLY)) {
xoap->xoa_readonly =
((fa->fa_attr & SMB_FA_RDONLY) != 0);
XVA_SET_RTN(xvap, XAT_READONLY);
}
if (XVA_ISSET_REQ(xvap, XAT_HIDDEN)) {
xoap->xoa_hidden =
((fa->fa_attr & SMB_FA_HIDDEN) != 0);
XVA_SET_RTN(xvap, XAT_HIDDEN);
}
}
void
smbfs_flushall(cred_t *cr)
{
smi_globals_t *smg;
smbmntinfo_t *tmp_smi, *cur_smi, *next_smi;
smg = zone_getspecific(smi_list_key, crgetzone(cr));
ASSERT(smg != NULL);
mutex_enter(&smg->smg_lock);
cur_smi = list_head(&smg->smg_list);
if (cur_smi == NULL) {
mutex_exit(&smg->smg_lock);
return;
}
VFS_HOLD(cur_smi->smi_vfsp);
mutex_exit(&smg->smg_lock);
flush:
smbfs_rflush(cur_smi->smi_vfsp, cr);
mutex_enter(&smg->smg_lock);
for (tmp_smi = list_head(&smg->smg_list);
tmp_smi != NULL;
tmp_smi = list_next(&smg->smg_list, tmp_smi))
if (tmp_smi == cur_smi)
break;
if (tmp_smi != NULL)
next_smi = list_next(&smg->smg_list, tmp_smi);
else
next_smi = list_head(&smg->smg_list);
if (next_smi != NULL)
VFS_HOLD(next_smi->smi_vfsp);
VFS_RELE(cur_smi->smi_vfsp);
mutex_exit(&smg->smg_lock);
if (next_smi != NULL) {
cur_smi = next_smi;
goto flush;
}
}
static void *
smbfs_zone_init(zoneid_t zoneid)
{
smi_globals_t *smg;
smg = kmem_alloc(sizeof (*smg), KM_SLEEP);
mutex_init(&smg->smg_lock, NULL, MUTEX_DEFAULT, NULL);
list_create(&smg->smg_list, sizeof (smbmntinfo_t),
offsetof(smbmntinfo_t, smi_zone_node));
smg->smg_destructor_called = B_FALSE;
return (smg);
}
static void
smbfs_zone_shutdown(zoneid_t zoneid, void *data)
{
smi_globals_t *smg = data;
smbmntinfo_t *smi;
ASSERT(smg != NULL);
again:
mutex_enter(&smg->smg_lock);
for (smi = list_head(&smg->smg_list); smi != NULL;
smi = list_next(&smg->smg_list, smi)) {
if (smi->smi_flags & SMI_DEAD)
continue;
VFS_HOLD(smi->smi_vfsp);
mutex_enter(&smi->smi_lock);
smi->smi_flags |= SMI_DEAD;
mutex_exit(&smi->smi_lock);
mutex_exit(&smg->smg_lock);
VFS_RELE(smi->smi_vfsp);
goto again;
}
mutex_exit(&smg->smg_lock);
}
static void
smbfs_zone_free_globals(smi_globals_t *smg)
{
list_destroy(&smg->smg_list);
mutex_destroy(&smg->smg_lock);
kmem_free(smg, sizeof (*smg));
}
static void
smbfs_zone_destroy(zoneid_t zoneid, void *data)
{
smi_globals_t *smg = data;
ASSERT(smg != NULL);
mutex_enter(&smg->smg_lock);
if (list_head(&smg->smg_list) != NULL) {
smg->smg_destructor_called = B_TRUE;
mutex_exit(&smg->smg_lock);
return;
}
smbfs_zone_free_globals(smg);
}
void
smbfs_zonelist_add(smbmntinfo_t *smi)
{
smi_globals_t *smg;
smg = zone_getspecific(smi_list_key, smi->smi_zone_ref.zref_zone);
mutex_enter(&smg->smg_lock);
list_insert_head(&smg->smg_list, smi);
mutex_exit(&smg->smg_lock);
}
void
smbfs_zonelist_remove(smbmntinfo_t *smi)
{
smi_globals_t *smg;
smg = zone_getspecific(smi_list_key, smi->smi_zone_ref.zref_zone);
mutex_enter(&smg->smg_lock);
list_remove(&smg->smg_list, smi);
if (list_head(&smg->smg_list) == NULL &&
smg->smg_destructor_called == B_TRUE) {
smbfs_zone_free_globals(smg);
return;
}
mutex_exit(&smg->smg_lock);
}
#ifdef lint
#define NEED_SMBFS_CALLBACKS 1
#endif
#ifdef NEED_SMBFS_CALLBACKS
static void smbfs_dead(smb_share_t *ssp)
{
}
static void smbfs_cb_nop(smb_share_t *ss)
{
}
smb_fscb_t smbfs_cb = {
.fscb_disconn = smbfs_dead,
.fscb_connect = smbfs_cb_nop
};
#endif
int
smbfs_clntinit(void)
{
zone_key_create(&smi_list_key, smbfs_zone_init, smbfs_zone_shutdown,
smbfs_zone_destroy);
#ifdef NEED_SMBFS_CALLBACKS
(void) smb_fscb_set(&smbfs_cb);
#endif
return (0);
}
void
smbfs_clntfini(void)
{
#ifdef NEED_SMBFS_CALLBACKS
(void) smb_fscb_set(NULL);
#endif
(void) zone_key_delete(smi_list_key);
}