#include <sys/stat.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/time.h>
#include <sys/varargs.h>
#include <sys/conf.h>
#include <sys/modctl.h>
#include <sys/vnode.h>
#include <fs/fs_subr.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/disp.h>
#include <sys/vscan.h>
#include <sys/policy.h>
#include <sys/sdt.h>
#define VS_DAEMON_WAIT_SEC 60
#define VS_NODENAME_LEN 16
uint32_t vs_reconnect_timeout = VS_DAEMON_WAIT_SEC;
extern uint32_t vs_nodes_max;
typedef enum {
VS_DRV_UNCONFIG,
VS_DRV_IDLE,
VS_DRV_CONNECTED,
VS_DRV_ENABLED,
VS_DRV_DELAYED_DISABLE
} vscan_drv_state_t;
static vscan_drv_state_t vscan_drv_state = VS_DRV_UNCONFIG;
typedef enum {
VS_DRV_INST_UNCONFIG = 0,
VS_DRV_INST_INIT,
VS_DRV_INST_OPEN,
VS_DRV_INST_READING
} vscan_drv_inst_state_t;
static vscan_drv_inst_state_t *vscan_drv_inst_state;
static int vscan_drv_inst_state_sz;
static dev_info_t *vscan_drv_dip;
static kmutex_t vscan_drv_mutex;
static kcondvar_t vscan_drv_cv;
static int vscan_drv_attach(dev_info_t *, ddi_attach_cmd_t);
static int vscan_drv_detach(dev_info_t *, ddi_detach_cmd_t);
static int vscan_drv_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int vscan_drv_open(dev_t *, int, int, cred_t *);
static int vscan_drv_close(dev_t, int, int, cred_t *);
static int vscan_drv_read(dev_t, struct uio *, cred_t *);
static int vscan_drv_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
static boolean_t vscan_drv_in_use(void);
static void vscan_drv_delayed_disable(void);
static struct cb_ops cbops = {
vscan_drv_open,
vscan_drv_close,
nodev,
nodev,
nodev,
vscan_drv_read,
nodev,
vscan_drv_ioctl,
nodev,
nodev,
nodev,
nochpoll,
ddi_prop_op,
NULL,
D_MP,
CB_REV,
nodev,
nodev,
};
static struct dev_ops devops = {
DEVO_REV,
0,
vscan_drv_getinfo,
nulldev,
nulldev,
vscan_drv_attach,
vscan_drv_detach,
nodev,
&cbops,
NULL,
NULL,
ddi_quiesce_not_needed,
};
static struct modldrv modldrv = {
&mod_driverops,
"virus scanning",
&devops,
};
static struct modlinkage modlinkage = {
MODREV_1,
&modldrv,
NULL,
};
int
_init(void)
{
int rc;
vscan_drv_inst_state_sz =
sizeof (vscan_drv_inst_state_t) * (vs_nodes_max + 1);
if (vscan_door_init() != 0)
return (DDI_FAILURE);
if (vscan_svc_init() != 0) {
vscan_door_fini();
return (DDI_FAILURE);
}
mutex_init(&vscan_drv_mutex, NULL, MUTEX_DRIVER, NULL);
vscan_drv_inst_state = kmem_zalloc(vscan_drv_inst_state_sz, KM_SLEEP);
cv_init(&vscan_drv_cv, NULL, CV_DEFAULT, NULL);
if ((rc = mod_install(&modlinkage)) != 0) {
vscan_door_fini();
vscan_svc_fini();
kmem_free(vscan_drv_inst_state, vscan_drv_inst_state_sz);
cv_destroy(&vscan_drv_cv);
mutex_destroy(&vscan_drv_mutex);
}
return (rc);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
int
_fini(void)
{
int rc;
if (vscan_drv_in_use())
return (EBUSY);
if ((rc = mod_remove(&modlinkage)) == 0) {
vscan_door_fini();
vscan_svc_fini();
kmem_free(vscan_drv_inst_state, vscan_drv_inst_state_sz);
cv_destroy(&vscan_drv_cv);
mutex_destroy(&vscan_drv_mutex);
}
return (rc);
}
static int
vscan_drv_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result)
{
ulong_t inst = getminor((dev_t)arg);
switch (cmd) {
case DDI_INFO_DEVT2DEVINFO:
*result = vscan_drv_dip;
return (DDI_SUCCESS);
case DDI_INFO_DEVT2INSTANCE:
*result = (void *)inst;
return (DDI_SUCCESS);
}
return (DDI_FAILURE);
}
static int
vscan_drv_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
if (cmd != DDI_ATTACH)
return (DDI_FAILURE);
if (ddi_get_instance(dip) != 0)
return (DDI_FAILURE);
vscan_drv_dip = dip;
if (vscan_drv_create_node(0) == B_FALSE)
return (DDI_FAILURE);
vscan_drv_state = VS_DRV_IDLE;
return (DDI_SUCCESS);
}
static int
vscan_drv_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
int i;
if (cmd != DDI_DETACH)
return (DDI_FAILURE);
if (ddi_get_instance(dip) != 0)
return (DDI_FAILURE);
if (vscan_drv_in_use())
return (DDI_FAILURE);
vscan_drv_dip = NULL;
ddi_remove_minor_node(dip, NULL);
for (i = 0; i <= vs_nodes_max; i++)
vscan_drv_inst_state[i] = VS_DRV_INST_UNCONFIG;
vscan_drv_state = VS_DRV_UNCONFIG;
return (DDI_SUCCESS);
}
static boolean_t
vscan_drv_in_use()
{
boolean_t in_use = B_FALSE;
mutex_enter(&vscan_drv_mutex);
if ((vscan_drv_state != VS_DRV_IDLE) &&
(vscan_drv_state != VS_DRV_UNCONFIG)) {
in_use = B_TRUE;
}
mutex_exit(&vscan_drv_mutex);
if (in_use)
return (B_TRUE);
else
return (vscan_svc_in_use());
}
static int
vscan_drv_open(dev_t *devp, int flag, int otyp, cred_t *credp)
{
int rc;
int inst = getminor(*devp);
if ((inst < 0) || (inst > vs_nodes_max))
return (EINVAL);
if ((rc = secpolicy_vscan(credp)) != 0) {
DTRACE_PROBE1(vscan__priv, int, rc);
return (EPERM);
}
mutex_enter(&vscan_drv_mutex);
if (inst == 0) {
switch (vscan_drv_state) {
case VS_DRV_IDLE:
vscan_drv_state = VS_DRV_CONNECTED;
break;
case VS_DRV_DELAYED_DISABLE:
cv_signal(&vscan_drv_cv);
vscan_drv_state = VS_DRV_CONNECTED;
break;
default:
DTRACE_PROBE1(vscan__drv__state__violation,
int, vscan_drv_state);
mutex_exit(&vscan_drv_mutex);
return (EINVAL);
}
} else {
if ((vscan_drv_state != VS_DRV_ENABLED) ||
(vscan_drv_inst_state[inst] != VS_DRV_INST_INIT)) {
mutex_exit(&vscan_drv_mutex);
return (EINVAL);
}
vscan_drv_inst_state[inst] = VS_DRV_INST_OPEN;
}
mutex_exit(&vscan_drv_mutex);
return (0);
}
static int
vscan_drv_close(dev_t dev, int flag, int otyp, cred_t *credp)
{
int i, inst = getminor(dev);
if ((inst < 0) || (inst > vs_nodes_max))
return (EINVAL);
mutex_enter(&vscan_drv_mutex);
if (inst != 0) {
vscan_drv_inst_state[inst] = VS_DRV_INST_INIT;
mutex_exit(&vscan_drv_mutex);
return (0);
}
if ((vscan_drv_state != VS_DRV_CONNECTED) &&
(vscan_drv_state != VS_DRV_ENABLED)) {
DTRACE_PROBE1(vscan__drv__state__violation,
int, vscan_drv_state);
mutex_exit(&vscan_drv_mutex);
return (EINVAL);
}
for (i = 1; i <= vs_nodes_max; i++) {
if (vscan_drv_inst_state[i] != VS_DRV_INST_UNCONFIG)
vscan_drv_inst_state[i] = VS_DRV_INST_INIT;
}
if (vscan_drv_state == VS_DRV_CONNECTED) {
vscan_drv_state = VS_DRV_IDLE;
} else {
cmn_err(CE_WARN, "Detected vscand exit without clean shutdown");
if (thread_create(NULL, 0, vscan_drv_delayed_disable,
0, 0, &p0, TS_RUN, minclsyspri) == NULL) {
vscan_svc_disable();
vscan_drv_state = VS_DRV_IDLE;
} else {
vscan_drv_state = VS_DRV_DELAYED_DISABLE;
}
}
mutex_exit(&vscan_drv_mutex);
vscan_svc_scan_abort();
vscan_door_close();
return (0);
}
static void
vscan_drv_delayed_disable(void)
{
mutex_enter(&vscan_drv_mutex);
(void) cv_reltimedwait(&vscan_drv_cv, &vscan_drv_mutex,
SEC_TO_TICK(vs_reconnect_timeout), TR_CLOCK_TICK);
if (vscan_drv_state == VS_DRV_DELAYED_DISABLE) {
vscan_svc_disable();
vscan_drv_state = VS_DRV_IDLE;
} else {
DTRACE_PROBE(vscan__reconnect);
}
mutex_exit(&vscan_drv_mutex);
}
static int
vscan_drv_read(dev_t dev, struct uio *uiop, cred_t *credp)
{
int rc;
int inst = getminor(dev);
vnode_t *vp;
if ((inst <= 0) || (inst > vs_nodes_max))
return (EINVAL);
mutex_enter(&vscan_drv_mutex);
if ((vscan_drv_state != VS_DRV_ENABLED) ||
(vscan_drv_inst_state[inst] != VS_DRV_INST_OPEN)) {
mutex_exit(&vscan_drv_mutex);
return (EINVAL);
}
vscan_drv_inst_state[inst] = VS_DRV_INST_READING;
mutex_exit(&vscan_drv_mutex);
if ((vp = vscan_svc_get_vnode(inst)) == NULL)
return (EINVAL);
(void) VOP_RWLOCK(vp, V_WRITELOCK_FALSE, NULL);
rc = VOP_READ(vp, uiop, 0, kcred, NULL);
VOP_RWUNLOCK(vp, V_WRITELOCK_FALSE, NULL);
mutex_enter(&vscan_drv_mutex);
if (vscan_drv_inst_state[inst] == VS_DRV_INST_READING)
vscan_drv_inst_state[inst] = VS_DRV_INST_OPEN;
mutex_exit(&vscan_drv_mutex);
return (rc);
}
static int
vscan_drv_ioctl(dev_t dev, int cmd, intptr_t arg, int mode,
cred_t *credp, int *rvalp)
{
int inst = getminor(dev);
vs_config_t conf;
vs_scan_rsp_t rsp;
if (inst != 0)
return (EINVAL);
switch (cmd) {
case VS_IOCTL_ENABLE:
mutex_enter(&vscan_drv_mutex);
if (vscan_drv_state != VS_DRV_CONNECTED) {
DTRACE_PROBE1(vscan__drv__state__violation,
int, vscan_drv_state);
mutex_exit(&vscan_drv_mutex);
return (EINVAL);
}
if ((vscan_door_open((int)arg) != 0) ||
(vscan_svc_enable() != 0)) {
mutex_exit(&vscan_drv_mutex);
return (EINVAL);
}
vscan_drv_state = VS_DRV_ENABLED;
mutex_exit(&vscan_drv_mutex);
break;
case VS_IOCTL_DISABLE:
mutex_enter(&vscan_drv_mutex);
if (vscan_drv_state != VS_DRV_ENABLED) {
DTRACE_PROBE1(vscan__drv__state__violation,
int, vscan_drv_state);
mutex_exit(&vscan_drv_mutex);
return (EINVAL);
}
vscan_svc_disable();
vscan_drv_state = VS_DRV_CONNECTED;
mutex_exit(&vscan_drv_mutex);
break;
case VS_IOCTL_RESULT:
if (ddi_copyin((void *)arg, &rsp,
sizeof (vs_scan_rsp_t), 0) == -1)
return (EFAULT);
else
vscan_svc_scan_result(&rsp);
break;
case VS_IOCTL_CONFIG:
if (ddi_copyin((void *)arg, &conf,
sizeof (vs_config_t), 0) == -1)
return (EFAULT);
if (vscan_svc_configure(&conf) == -1)
return (EINVAL);
break;
case VS_IOCTL_MAX_REQ:
if (ddi_copyout(&vs_nodes_max, (void *)arg,
sizeof (uint32_t), 0) == -1)
return (EFAULT);
break;
default:
return (ENOTTY);
}
return (0);
}
boolean_t
vscan_drv_create_node(int idx)
{
char name[VS_NODENAME_LEN];
boolean_t rc = B_TRUE;
mutex_enter(&vscan_drv_mutex);
if (vscan_drv_inst_state[idx] == VS_DRV_INST_UNCONFIG) {
(void) snprintf(name, VS_NODENAME_LEN, "vscan%d", idx);
if (ddi_create_minor_node(vscan_drv_dip, name,
S_IFCHR, idx, DDI_PSEUDO, 0) == DDI_SUCCESS) {
vscan_drv_inst_state[idx] = VS_DRV_INST_INIT;
} else {
rc = B_FALSE;
}
DTRACE_PROBE2(vscan__minor__node, int, idx, int, rc);
}
mutex_exit(&vscan_drv_mutex);
return (rc);
}