root/usr/src/uts/common/io/vscan/vscan_svc.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 * Copyright (c) 2015, Joyent, Inc.
 */

#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/cmn_err.h>
#include <sys/vnode.h>
#include <fs/fs_subr.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/disp.h>
#include <sys/sdt.h>
#include <sys/cred.h>
#include <sys/list.h>
#include <sys/vscan.h>
#include <sys/sysmacros.h>

#define VS_REQ_MAGIC            0x52515354 /* 'RQST' */

#define VS_REQS_DEFAULT         20000   /* pending scan requests - reql */
#define VS_NODES_DEFAULT        128     /* concurrent file scans */
#define VS_WORKERS_DEFAULT      32      /* worker threads */
#define VS_SCANWAIT_DEFAULT     15*60   /* seconds to wait for scan result */
#define VS_REQL_HANDLER_TIMEOUT 30
#define VS_EXT_RECURSE_DEPTH    8

/* access derived from scan result (VS_STATUS_XXX) and file attributes */
#define VS_ACCESS_UNDEFINED     0
#define VS_ACCESS_ALLOW         1       /* return 0 */
#define VS_ACCESS_DENY          2       /* return EACCES */

#define tolower(C)      (((C) >= 'A' && (C) <= 'Z') ? (C) - 'A' + 'a' : (C))

/* global variables - tunable via /etc/system */
uint32_t vs_reqs_max = VS_REQS_DEFAULT; /* max scan requests */
uint32_t vs_nodes_max = VS_NODES_DEFAULT; /* max in-progress scan requests */
uint32_t vs_workers = VS_WORKERS_DEFAULT; /* max workers send reqs to vscand */
uint32_t vs_scan_wait = VS_SCANWAIT_DEFAULT; /* secs to wait for scan result */


/*
 * vscan_svc_state
 *
 *   +-----------------+
 *   | VS_SVC_UNCONFIG |
 *   +-----------------+
 *      |           ^
 *      | svc_init  | svc_fini
 *      v           |
 *   +-----------------+
 *   | VS_SVC_IDLE     |<----|
 *   +-----------------+         |
 *      |                    |
 *      | svc_enable         |
 *      |<----------------|  |
 *      v                 |  |
 *   +-----------------+  |  |
 *   | VS_SVC_ENABLED  |--|  |
 *   +-----------------+     |
 *      |                    |
 *      | svc_disable        | handler thread exit,
 *      v                    | all requests complete
 *   +-----------------+         |
 *   | VS_SVC_DISABLED |-----|
 *   +-----------------+
 *
 * svc_enable may occur when we are already in the ENABLED
 * state if vscand has exited without clean shutdown and
 * then reconnected within the delayed disable time period
 * (vs_reconnect_timeout) - see vscan_drv
 */

typedef enum {
        VS_SVC_UNCONFIG,
        VS_SVC_IDLE,
        VS_SVC_ENABLED, /* service enabled and registered */
        VS_SVC_DISABLED /* service disabled and nunregistered */
} vscan_svc_state_t;
static vscan_svc_state_t vscan_svc_state = VS_SVC_UNCONFIG;


/*
 * vscan_svc_req_state
 *
 * When a scan request is received from the file system it is
 * identified in or inserted into the vscan_svc_reql (INIT).
 * If the request is asynchronous 0 is then returned to the caller.
 * If the request is synchronous the req's refcnt is incremented
 * and the caller waits for the request to complete.
 * The refcnt is also incremented when the request is inserted
 * in vscan_svc_nodes, and decremented on scan_complete.
 *
 * vscan_svc_handler processes requests from the request list,
 * inserting them into vscan_svc_nodes and the task queue (QUEUED).
 * When the task queue call back (vscan_svc_do_scan) is invoked
 * the request transitions to IN_PROGRESS state. If the request
 * is sucessfully sent to vscand (door_call) and the door response
 * is SCANNING then the scan result will be received asynchronously.
 * Although unusual, it is possible that the async response is
 * received before the door call returns (hence the ASYNC_COMPLETE
 * state).
 * When the result has been determined / received,
 * vscan_svc_scan_complete is invoked to transition the request to
 * COMPLETE state, decrement refcnt and signal all waiting callers.
 * When the last waiting caller has processed the result (refcnt == 0)
 * the request is removed from vscan_svc_reql and vscan_svc_nodes
 * and deleted.
 *
 *      |                                                     ^
 *      | reql_insert                                         | refcnt == 0
 *      v                                                     | (delete)
 *   +------------------------+                   +---------------------+
 *   | VS_SVC_REQ_INIT        | -----DISABLE----> | VS_SVC_REQ_COMPLETE |
 *   +------------------------+                   +---------------------+
 *      |                                                     ^
 *      | insert_req, tq_dispatch                             |
 *      v                                                     |
 *   +------------------------+                               |
 *   | VS_SVC_REQ_QUEUED      |                           scan_complete
 *   +------------------------+                               |
 *      |                                                     |
 *      | tq_callback (do_scan)                               |
 *      |                                                     |
 *      v                        scan not req'd, error,       |
 *   +------------------------+  or door_result != SCANNING   |
 *   | VS_SVC_REQ_IN_PROGRESS |----------------->-------------|
 *   +------------------------+                               |
 *       |         |                                          |
 *       |         | door_result == SCANNING                  |
 *       |         v                                          |
 *       |     +---------------------------+    async result  |
 *       |     | VS_SVC_REQ_SCANNING       |-------->---------|
 *       |     +---------------------------+                  |
 *       |                                                    |
 *       | async result                                       |
 *       v                                                    |
 *    +---------------------------+      door_result = SCANNING   |
 *    | VS_SVC_REQ_ASYNC_COMPLETE |-------->------------------|
 *    +---------------------------+
 */
typedef enum {
        VS_SVC_REQ_INIT,
        VS_SVC_REQ_QUEUED,
        VS_SVC_REQ_IN_PROGRESS,
        VS_SVC_REQ_SCANNING,
        VS_SVC_REQ_ASYNC_COMPLETE,
        VS_SVC_REQ_COMPLETE
} vscan_svc_req_state_t;


/*
 * vscan_svc_reql - the list of pending and in-progress scan requests
 */
typedef struct vscan_req {
        uint32_t vsr_magic;     /* VS_REQ_MAGIC */
        list_node_t vsr_lnode;
        vnode_t *vsr_vp;
        uint32_t vsr_idx;       /* vscan_svc_nodes index */
        uint32_t vsr_seqnum;    /* unigue request id */
        uint32_t vsr_refcnt;
        kcondvar_t vsr_cv;
        vscan_svc_req_state_t vsr_state;
} vscan_req_t;

static list_t vscan_svc_reql;


/*
 * vscan_svc_nodes - table of files being scanned
 *
 * The index into this table is passed in the door call to
 * vscand. vscand uses the idx to determine which minor node
 * to open to read the file data. Within the kernel driver
 * the minor device number can thus be used to identify the
 * table index to get the appropriate vnode.
 *
 * Instance 0 is reserved for the daemon/driver control
 * interface: enable/configure/disable
 */
typedef struct vscan_svc_node {
        vscan_req_t *vsn_req;
        uint8_t vsn_quarantined;
        uint8_t vsn_modified;
        uint64_t vsn_size;
        timestruc_t vsn_mtime;
        vs_scanstamp_t vsn_scanstamp;
        uint32_t vsn_result;
        uint32_t vsn_access;
} vscan_svc_node_t;

static vscan_svc_node_t *vscan_svc_nodes;
static int vscan_svc_nodes_sz;


/* vscan_svc_taskq - queue of requests waiting to be sent to vscand */
static taskq_t *vscan_svc_taskq = NULL;

/* counts of entries in vscan_svc_reql, vscan_svc_nodes & vscan_svc_taskq */
typedef struct {
        uint32_t vsc_reql;
        uint32_t vsc_node;
        uint32_t vsc_tq;
} vscan_svc_counts_t;
static vscan_svc_counts_t vscan_svc_counts;

/*
 * vscan_svc_mutex protects the data pertaining to scan requests:
 * request list - vscan_svc_reql
 * node table - vscan_svc_nodes
 */
static kmutex_t vscan_svc_mutex;

/* unique request id for vscand request/response correlation */
static uint32_t vscan_svc_seqnum = 0;

/*
 * vscan_svc_cfg_mutex protects the configuration data:
 * vscan_svc_config, vscan_svc_types
 */
static kmutex_t vscan_svc_cfg_mutex;

/* configuration data - for virus scan exemption */
static vs_config_t vscan_svc_config;
static char *vscan_svc_types[VS_TYPES_MAX];

/* thread to insert reql entries into vscan_svc_nodes & vscan_svc_taskq */
static kthread_t *vscan_svc_reql_thread;
static kcondvar_t vscan_svc_reql_cv;
static vscan_req_t *vscan_svc_reql_next; /* next pending scan request */

/* local functions */
int vscan_svc_scan_file(vnode_t *, cred_t *, int);
static void vscan_svc_taskq_callback(void *);
static int vscan_svc_exempt_file(vnode_t *, boolean_t *);
static int vscan_svc_exempt_filetype(char *);
static int vscan_svc_match_ext(char *, char *, int);
static void vscan_svc_do_scan(vscan_req_t *);
static vs_scan_req_t *vscan_svc_populate_req(int);
static void vscan_svc_process_scan_result(int);
static void vscan_svc_scan_complete(vscan_req_t *);
static void vscan_svc_delete_req(vscan_req_t *);
static int vscan_svc_insert_req(vscan_req_t *);
static void vscan_svc_remove_req(int);
static vscan_req_t *vscan_svc_reql_find(vnode_t *);
static vscan_req_t *vscan_svc_reql_insert(vnode_t *);
static void vscan_svc_reql_remove(vscan_req_t *);

static int vscan_svc_getattr(int);
static int vscan_svc_setattr(int, int);

/* thread to insert reql entries into vscan_svc_nodes & vscan_svc_taskq */
static void vscan_svc_reql_handler(void);


/*
 * vscan_svc_init
 */
int
vscan_svc_init()
{
        if (vscan_svc_state != VS_SVC_UNCONFIG) {
                DTRACE_PROBE1(vscan__svc__state__violation,
                    int, vscan_svc_state);
                return (-1);
        }

        mutex_init(&vscan_svc_mutex, NULL, MUTEX_DEFAULT, NULL);
        mutex_init(&vscan_svc_cfg_mutex, NULL, MUTEX_DEFAULT, NULL);
        cv_init(&vscan_svc_reql_cv, NULL, CV_DEFAULT, NULL);

        vscan_svc_nodes_sz = sizeof (vscan_svc_node_t) * (vs_nodes_max + 1);
        vscan_svc_nodes = kmem_zalloc(vscan_svc_nodes_sz, KM_SLEEP);

        vscan_svc_counts.vsc_reql = 0;
        vscan_svc_counts.vsc_node = 0;
        vscan_svc_counts.vsc_tq = 0;

        vscan_svc_state = VS_SVC_IDLE;

        return (0);
}


/*
 * vscan_svc_fini
 */
void
vscan_svc_fini()
{
        if (vscan_svc_state != VS_SVC_IDLE) {
                DTRACE_PROBE1(vscan__svc__state__violation,
                    int, vscan_svc_state);
                return;
        }

        kmem_free(vscan_svc_nodes, vscan_svc_nodes_sz);

        cv_destroy(&vscan_svc_reql_cv);
        mutex_destroy(&vscan_svc_mutex);
        mutex_destroy(&vscan_svc_cfg_mutex);
        vscan_svc_state = VS_SVC_UNCONFIG;
}


/*
 * vscan_svc_enable
 */
int
vscan_svc_enable(void)
{
        mutex_enter(&vscan_svc_mutex);

        switch (vscan_svc_state) {
        case VS_SVC_ENABLED:
                /*
                 * it's possible (and okay) for vscan_svc_enable to be
                 * called when already enabled if vscand reconnects
                 * during a delayed disable
                 */
                break;
        case VS_SVC_IDLE:
                list_create(&vscan_svc_reql, sizeof (vscan_req_t),
                    offsetof(vscan_req_t, vsr_lnode));
                vscan_svc_reql_next = list_head(&vscan_svc_reql);

                vscan_svc_taskq = taskq_create("vscan_taskq", vs_workers,
                    MINCLSYSPRI, 1, INT_MAX, TASKQ_DYNAMIC);
                ASSERT(vscan_svc_taskq != NULL);

                vscan_svc_reql_thread = thread_create(NULL, 0,
                    vscan_svc_reql_handler, 0, 0, &p0, TS_RUN, MINCLSYSPRI);
                ASSERT(vscan_svc_reql_thread != NULL);

                /* ready to start processing requests */
                vscan_svc_state = VS_SVC_ENABLED;
                fs_vscan_register(vscan_svc_scan_file);
                break;
        default:
                DTRACE_PROBE1(vscan__svc__state__violation,
                    int, vscan_svc_state);
                return (-1);
        }

        mutex_exit(&vscan_svc_mutex);
        return (0);
}


/*
 * vscan_svc_disable
 *
 * Resources allocated during vscan_svc_enable are free'd by
 * the handler thread immediately prior to exiting
 */
void
vscan_svc_disable(void)
{
        mutex_enter(&vscan_svc_mutex);

        switch (vscan_svc_state) {
        case VS_SVC_ENABLED:
                fs_vscan_register(NULL);
                vscan_svc_state = VS_SVC_DISABLED;
                cv_signal(&vscan_svc_reql_cv); /* wake handler thread */
                break;
        default:
                DTRACE_PROBE1(vscan__svc__state__violation, int,
                    vscan_svc_state);
        }

        mutex_exit(&vscan_svc_mutex);
}


/*
 * vscan_svc_in_use
 */
boolean_t
vscan_svc_in_use()
{
        boolean_t in_use;

        mutex_enter(&vscan_svc_mutex);

        switch (vscan_svc_state) {
        case VS_SVC_IDLE:
        case VS_SVC_UNCONFIG:
                in_use = B_FALSE;
                break;
        default:
                in_use = B_TRUE;
                break;
        }

        mutex_exit(&vscan_svc_mutex);
        return (in_use);
}


/*
 * vscan_svc_get_vnode
 *
 * Get the file vnode indexed by idx.
 */
vnode_t *
vscan_svc_get_vnode(int idx)
{
        vnode_t *vp = NULL;

        ASSERT(idx > 0);
        ASSERT(idx <= vs_nodes_max);

        mutex_enter(&vscan_svc_mutex);
        if (vscan_svc_nodes[idx].vsn_req)
                vp = vscan_svc_nodes[idx].vsn_req->vsr_vp;
        mutex_exit(&vscan_svc_mutex);

        return (vp);
}


/*
 * vscan_svc_scan_file
 *
 * This function is the entry point for the file system to
 * request that a file be virus scanned.
 */
int
vscan_svc_scan_file(vnode_t *vp, cred_t *cr, int async)
{
        int access;
        vscan_req_t *req;
        boolean_t allow;
        clock_t timeout, time_left;

        if ((vp == NULL) || (vp->v_path == vn_vpath_empty) || cr == NULL)
                return (0);

        DTRACE_PROBE2(vscan__scan__file, char *, vp->v_path, int, async);

        /* check if size or type exempts file from scanning */
        if (vscan_svc_exempt_file(vp, &allow)) {
                if ((allow == B_TRUE) || (async != 0))
                        return (0);

                return (EACCES);
        }

        mutex_enter(&vscan_svc_mutex);

        if (vscan_svc_state != VS_SVC_ENABLED) {
                DTRACE_PROBE1(vscan__svc__state__violation,
                    int, vscan_svc_state);
                mutex_exit(&vscan_svc_mutex);
                return (0);
        }

        /* insert (or find) request in list */
        if ((req = vscan_svc_reql_insert(vp)) == NULL) {
                mutex_exit(&vscan_svc_mutex);
                cmn_err(CE_WARN, "Virus scan request list full");
                return ((async != 0) ? 0 : EACCES);
        }

        /* asynchronous request: return 0 */
        if (async) {
                mutex_exit(&vscan_svc_mutex);
                return (0);
        }

        /* synchronous scan request: wait for result */
        ++(req->vsr_refcnt);
        time_left = SEC_TO_TICK(vs_scan_wait);
        while ((time_left > 0) && (req->vsr_state != VS_SVC_REQ_COMPLETE)) {
                timeout = time_left;
                time_left = cv_reltimedwait_sig(&(req->vsr_cv),
                    &vscan_svc_mutex, timeout, TR_CLOCK_TICK);
        }

        if (time_left == -1) {
                cmn_err(CE_WARN, "Virus scan request timeout %s (%d) \n",
                    vp->v_path, req->vsr_seqnum);
                DTRACE_PROBE1(vscan__scan__timeout, vscan_req_t *, req);
        }

        ASSERT(req->vsr_magic == VS_REQ_MAGIC);
        if (vscan_svc_state == VS_SVC_DISABLED)
                access = VS_ACCESS_ALLOW;
        else if (req->vsr_idx == 0)
                access = VS_ACCESS_DENY;
        else
                access = vscan_svc_nodes[req->vsr_idx].vsn_access;

        if ((--req->vsr_refcnt) == 0)
                vscan_svc_delete_req(req);

        mutex_exit(&vscan_svc_mutex);
        return ((access == VS_ACCESS_ALLOW) ? 0 : EACCES);
}


/*
 * vscan_svc_reql_handler
 *
 * inserts scan requests (from vscan_svc_reql) into
 * vscan_svc_nodes and vscan_svc_taskq
 */
static void
vscan_svc_reql_handler(void)
{
        vscan_req_t *req, *next;

        for (;;) {
                mutex_enter(&vscan_svc_mutex);

                if ((vscan_svc_state == VS_SVC_DISABLED) &&
                    (vscan_svc_counts.vsc_reql == 0)) {
                        /* free resources allocated durining enable */
                        taskq_destroy(vscan_svc_taskq);
                        vscan_svc_taskq = NULL;
                        list_destroy(&vscan_svc_reql);
                        vscan_svc_state = VS_SVC_IDLE;
                        mutex_exit(&vscan_svc_mutex);
                        return;
                }

                /*
                 * If disabled, scan_complete any pending requests.
                 * Otherwise insert pending requests into vscan_svc_nodes
                 * and vscan_svc_taskq. If no slots are available in
                 * vscan_svc_nodes break loop and wait for one
                 */
                req = vscan_svc_reql_next;

                while (req != NULL) {
                        ASSERT(req->vsr_magic == VS_REQ_MAGIC);
                        next = list_next(&vscan_svc_reql, req);

                        if (vscan_svc_state == VS_SVC_DISABLED) {
                                vscan_svc_scan_complete(req);
                        } else {
                                /* insert request into vscan_svc_nodes */
                                if (vscan_svc_insert_req(req) == -1)
                                        break;

                                /* add the scan request into the taskq */
                                (void) taskq_dispatch(vscan_svc_taskq,
                                    vscan_svc_taskq_callback,
                                    (void *)req, TQ_SLEEP);
                                ++(vscan_svc_counts.vsc_tq);

                                req->vsr_state = VS_SVC_REQ_QUEUED;
                        }
                        req = next;
                }

                vscan_svc_reql_next = req;

                DTRACE_PROBE2(vscan__req__counts, char *, "handler wait",
                    vscan_svc_counts_t *, &vscan_svc_counts);

                (void) cv_reltimedwait(&vscan_svc_reql_cv, &vscan_svc_mutex,
                    SEC_TO_TICK(VS_REQL_HANDLER_TIMEOUT), TR_CLOCK_TICK);

                DTRACE_PROBE2(vscan__req__counts, char *, "handler wake",
                    vscan_svc_counts_t *, &vscan_svc_counts);

                mutex_exit(&vscan_svc_mutex);
        }
}


static void
vscan_svc_taskq_callback(void *data)
{
        vscan_req_t *req;

        mutex_enter(&vscan_svc_mutex);

        req = (vscan_req_t *)data;
        ASSERT(req->vsr_magic == VS_REQ_MAGIC);
        vscan_svc_do_scan(req);
        if (req->vsr_state != VS_SVC_REQ_SCANNING)
                vscan_svc_scan_complete(req);

        --(vscan_svc_counts.vsc_tq);
        mutex_exit(&vscan_svc_mutex);
}


/*
 * vscan_svc_do_scan
 *
 * Note: To avoid potential deadlock it is important that
 * vscan_svc_mutex is not held during the call to
 * vscan_drv_create_note. vscan_drv_create_note enters
 * the vscan_drv_mutex and it is possible that a thread
 * holding that mutex could be waiting for vscan_svc_mutex.
 */
static void
vscan_svc_do_scan(vscan_req_t *req)
{
        int idx, result;
        vscan_svc_node_t *node;
        vs_scan_req_t *door_req;

        ASSERT(MUTEX_HELD(&vscan_svc_mutex));

        idx = req->vsr_idx;
        node = &vscan_svc_nodes[idx];

        req->vsr_state = VS_SVC_REQ_IN_PROGRESS;

        /* if vscan not enabled (shutting down), allow ACCESS */
        if (vscan_svc_state != VS_SVC_ENABLED) {
                node->vsn_access = VS_ACCESS_ALLOW;
                return;
        }

        if (vscan_svc_getattr(idx) != 0) {
                cmn_err(CE_WARN, "Can't access xattr for %s\n",
                    req->vsr_vp->v_path);
                node->vsn_access = VS_ACCESS_DENY;
                return;
        }

        /* valid scan_req ptr guaranteed */
        door_req = vscan_svc_populate_req(idx);

        /* free up mutex around create node and door call */
        mutex_exit(&vscan_svc_mutex);
        if (vscan_drv_create_node(idx) != B_TRUE)
                result = VS_STATUS_ERROR;
        else
                result = vscan_door_scan_file(door_req);
        kmem_free(door_req, sizeof (vs_scan_req_t));
        mutex_enter(&vscan_svc_mutex);

        if (result != VS_STATUS_SCANNING) {
                vscan_svc_nodes[idx].vsn_result = result;
                vscan_svc_process_scan_result(idx);
        } else { /* async response */
                if (req->vsr_state == VS_SVC_REQ_IN_PROGRESS)
                        req->vsr_state = VS_SVC_REQ_SCANNING;
        }
}


/*
 * vscan_svc_populate_req
 *
 * Allocate a scan request to be sent to vscand, populating it
 * from the data in vscan_svc_nodes[idx].
 *
 * Returns: scan request object
 */
static vs_scan_req_t *
vscan_svc_populate_req(int idx)
{
        vs_scan_req_t *scan_req;
        vscan_req_t *req;
        vscan_svc_node_t *node;

        ASSERT(MUTEX_HELD(&vscan_svc_mutex));

        node = &vscan_svc_nodes[idx];
        req = node->vsn_req;
        scan_req = kmem_zalloc(sizeof (vs_scan_req_t), KM_SLEEP);

        scan_req->vsr_idx = idx;
        scan_req->vsr_seqnum = req->vsr_seqnum;
        (void) strncpy(scan_req->vsr_path, req->vsr_vp->v_path, MAXPATHLEN);
        scan_req->vsr_size = node->vsn_size;
        scan_req->vsr_modified = node->vsn_modified;
        scan_req->vsr_quarantined = node->vsn_quarantined;
        scan_req->vsr_flags = 0;
        (void) strncpy(scan_req->vsr_scanstamp,
            node->vsn_scanstamp, sizeof (vs_scanstamp_t));

        return (scan_req);
}


/*
 * vscan_svc_scan_complete
 */
static void
vscan_svc_scan_complete(vscan_req_t *req)
{
        ASSERT(MUTEX_HELD(&vscan_svc_mutex));
        ASSERT(req != NULL);

        req->vsr_state = VS_SVC_REQ_COMPLETE;

        if ((--req->vsr_refcnt) == 0)
                vscan_svc_delete_req(req);
        else
                cv_broadcast(&(req->vsr_cv));
}


/*
 * vscan_svc_delete_req
 */
static void
vscan_svc_delete_req(vscan_req_t *req)
{
        int idx;

        ASSERT(MUTEX_HELD(&vscan_svc_mutex));
        ASSERT(req != NULL);
        ASSERT(req->vsr_refcnt == 0);
        ASSERT(req->vsr_state == VS_SVC_REQ_COMPLETE);

        if ((idx = req->vsr_idx) != 0)
                vscan_svc_remove_req(idx);

        vscan_svc_reql_remove(req);

        cv_signal(&vscan_svc_reql_cv);
}


/*
 * vscan_svc_scan_result
 *
 * Invoked from vscan_drv.c on receipt of an ioctl containing
 * an async scan result (VS_DRV_IOCTL_RESULT)
 * If the vsr_seqnum in the response does not match that in the
 * vscan_svc_nodes entry the result is discarded.
 */
void
vscan_svc_scan_result(vs_scan_rsp_t *scan_rsp)
{
        vscan_req_t *req;
        vscan_svc_node_t *node;

        mutex_enter(&vscan_svc_mutex);

        node = &vscan_svc_nodes[scan_rsp->vsr_idx];

        if ((req = node->vsn_req) == NULL) {
                mutex_exit(&vscan_svc_mutex);
                return;
        }

        ASSERT(req->vsr_magic == VS_REQ_MAGIC);

        if (scan_rsp->vsr_seqnum != req->vsr_seqnum) {
                mutex_exit(&vscan_svc_mutex);
                return;
        }

        node->vsn_result = scan_rsp->vsr_result;
        (void) strncpy(node->vsn_scanstamp,
            scan_rsp->vsr_scanstamp, sizeof (vs_scanstamp_t));

        vscan_svc_process_scan_result(scan_rsp->vsr_idx);

        if (node->vsn_req->vsr_state == VS_SVC_REQ_SCANNING)
                vscan_svc_scan_complete(node->vsn_req);
        else
                node->vsn_req->vsr_state = VS_SVC_REQ_ASYNC_COMPLETE;

        mutex_exit(&vscan_svc_mutex);
}


/*
 * vscan_svc_scan_abort
 *
 * Abort in-progress scan requests.
 */
void
vscan_svc_scan_abort()
{
        int idx;
        vscan_req_t *req;

        mutex_enter(&vscan_svc_mutex);

        for (idx = 1; idx <= vs_nodes_max; idx++) {
                if ((req = vscan_svc_nodes[idx].vsn_req) == NULL)
                        continue;

                ASSERT(req->vsr_magic == VS_REQ_MAGIC);

                if (req->vsr_state == VS_SVC_REQ_SCANNING) {
                        DTRACE_PROBE1(vscan__abort, vscan_req_t *, req);
                        vscan_svc_process_scan_result(idx);
                        vscan_svc_scan_complete(req);
                }
        }

        mutex_exit(&vscan_svc_mutex);
}


/*
 * vscan_svc_process_scan_result
 *
 * Sets vsn_access and updates file attributes based on vsn_result,
 * as follows:
 *
 * VS_STATUS_INFECTED
 *  deny access, set quarantine attribute, clear scanstamp
 * VS_STATUS_CLEAN
 *  allow access, set scanstamp,
 *  if file not modified since scan initiated, clear modified attribute
 * VS_STATUS_NO_SCAN
 *  deny access if file quarantined, otherwise allow access
 * VS_STATUS_UNDEFINED, VS_STATUS_ERROR
 *  deny access if file quarantined, modified or no scanstamp
 *  otherwise, allow access
 */
static void
vscan_svc_process_scan_result(int idx)
{
        struct vattr attr;
        vnode_t *vp;
        timestruc_t *mtime;
        vscan_svc_node_t *node;

        ASSERT(MUTEX_HELD(&vscan_svc_mutex));

        node = &vscan_svc_nodes[idx];

        switch (node->vsn_result) {
        case VS_STATUS_INFECTED:
                node->vsn_access = VS_ACCESS_DENY;
                node->vsn_quarantined = 1;
                node->vsn_scanstamp[0] = '\0';
                (void) vscan_svc_setattr(idx,
                    XAT_AV_QUARANTINED | XAT_AV_SCANSTAMP);
                break;

        case VS_STATUS_CLEAN:
                node->vsn_access = VS_ACCESS_ALLOW;

                /* if mtime has changed, don't clear the modified attribute */
                vp = node->vsn_req->vsr_vp;
                mtime = &(node->vsn_mtime);
                attr.va_mask = AT_MTIME;
                if ((VOP_GETATTR(vp, &attr, 0, kcred, NULL) != 0) ||
                    (mtime->tv_sec != attr.va_mtime.tv_sec) ||
                    (mtime->tv_nsec != attr.va_mtime.tv_nsec)) {
                        DTRACE_PROBE1(vscan__mtime__changed, vscan_svc_node_t *,
                            node);
                        (void) vscan_svc_setattr(idx, XAT_AV_SCANSTAMP);
                        break;
                }

                node->vsn_modified = 0;
                (void) vscan_svc_setattr(idx,
                    XAT_AV_SCANSTAMP | XAT_AV_MODIFIED);
                break;

        case VS_STATUS_NO_SCAN:
                if (node->vsn_quarantined)
                        node->vsn_access = VS_ACCESS_DENY;
                else
                        node->vsn_access = VS_ACCESS_ALLOW;
                break;

        case VS_STATUS_ERROR:
        case VS_STATUS_UNDEFINED:
        default:
                if ((node->vsn_quarantined) ||
                    (node->vsn_modified) ||
                    (node->vsn_scanstamp[0] == '\0'))
                        node->vsn_access = VS_ACCESS_DENY;
                else
                        node->vsn_access = VS_ACCESS_ALLOW;
                break;
        }

        DTRACE_PROBE4(vscan__result,
            int, idx, int, node->vsn_req->vsr_seqnum,
            int, node->vsn_result, int, node->vsn_access);
}


/*
 * vscan_svc_getattr
 *
 * Get the vscan related system attributes, AT_SIZE & AT_MTIME.
 */
static int
vscan_svc_getattr(int idx)
{
        xvattr_t xvattr;
        xoptattr_t *xoap = NULL;
        vnode_t *vp;
        vscan_svc_node_t *node;

        ASSERT(MUTEX_HELD(&vscan_svc_mutex));

        node = &vscan_svc_nodes[idx];
        if ((vp = node->vsn_req->vsr_vp) == NULL)
                return (-1);

        /* get the attributes */
        xva_init(&xvattr); /* sets AT_XVATTR */

        xvattr.xva_vattr.va_mask |= AT_SIZE;
        xvattr.xva_vattr.va_mask |= AT_MTIME;
        XVA_SET_REQ(&xvattr, XAT_AV_MODIFIED);
        XVA_SET_REQ(&xvattr, XAT_AV_QUARANTINED);
        XVA_SET_REQ(&xvattr, XAT_AV_SCANSTAMP);

        if (VOP_GETATTR(vp, (vattr_t *)&xvattr, 0, kcred, NULL) != 0)
                return (-1);

        if ((xoap = xva_getxoptattr(&xvattr)) == NULL) {
                cmn_err(CE_NOTE, "Virus scan request failed; "
                    "file system does not support virus scanning");
                return (-1);
        }

        node->vsn_size = xvattr.xva_vattr.va_size;
        node->vsn_mtime.tv_sec = xvattr.xva_vattr.va_mtime.tv_sec;
        node->vsn_mtime.tv_nsec = xvattr.xva_vattr.va_mtime.tv_nsec;

        if (XVA_ISSET_RTN(&xvattr, XAT_AV_MODIFIED) == 0)
                return (-1);
        node->vsn_modified = xoap->xoa_av_modified;

        if (XVA_ISSET_RTN(&xvattr, XAT_AV_QUARANTINED) == 0)
                return (-1);
        node->vsn_quarantined = xoap->xoa_av_quarantined;

        if (XVA_ISSET_RTN(&xvattr, XAT_AV_SCANSTAMP) != 0) {
                (void) memcpy(node->vsn_scanstamp,
                    xoap->xoa_av_scanstamp, AV_SCANSTAMP_SZ);
        }

        DTRACE_PROBE1(vscan__getattr, vscan_svc_node_t *, node);
        return (0);
}


/*
 * vscan_svc_setattr
 *
 * Set the vscan related system attributes.
 */
static int
vscan_svc_setattr(int idx, int which)
{
        xvattr_t xvattr;
        xoptattr_t *xoap = NULL;
        vnode_t *vp;
        int len;
        vscan_svc_node_t *node;

        ASSERT(MUTEX_HELD(&vscan_svc_mutex));

        node = &vscan_svc_nodes[idx];
        if ((vp = node->vsn_req->vsr_vp) == NULL)
                return (-1);

        /* update the attributes */
        xva_init(&xvattr); /* sets AT_XVATTR */
        if ((xoap = xva_getxoptattr(&xvattr)) == NULL)
                return (-1);

        if (which & XAT_AV_MODIFIED) {
                XVA_SET_REQ(&xvattr, XAT_AV_MODIFIED);
                xoap->xoa_av_modified = node->vsn_modified;
        }

        if (which & XAT_AV_QUARANTINED) {
                XVA_SET_REQ(&xvattr, XAT_AV_QUARANTINED);
                xoap->xoa_av_quarantined = node->vsn_quarantined;
        }

        if (which & XAT_AV_SCANSTAMP) {
                XVA_SET_REQ(&xvattr, XAT_AV_SCANSTAMP);
                len = strlen(node->vsn_scanstamp);
                (void) memcpy(xoap->xoa_av_scanstamp,
                    node->vsn_scanstamp, len);
        }

        /* if access is denied, set mtime to invalidate client cache */
        if (node->vsn_access != VS_ACCESS_ALLOW) {
                xvattr.xva_vattr.va_mask |= AT_MTIME;
                gethrestime(&xvattr.xva_vattr.va_mtime);
        }

        if (VOP_SETATTR(vp, (vattr_t *)&xvattr, 0, kcred, NULL) != 0)
                return (-1);

        DTRACE_PROBE2(vscan__setattr,
            vscan_svc_node_t *, node, int, which);

        return (0);
}


/*
 * vscan_svc_configure
 *
 * store configuration in vscan_svc_config
 * set up vscan_svc_types array of pointers into
 * vscan_svc_config.vsc_types for efficient searching
 */
int
vscan_svc_configure(vs_config_t *conf)
{
        int count = 0;
        char *p, *beg, *end;

        mutex_enter(&vscan_svc_cfg_mutex);

        vscan_svc_config = *conf;

        (void) memset(vscan_svc_types, 0, sizeof (vscan_svc_types));

        beg = vscan_svc_config.vsc_types;
        end = beg + vscan_svc_config.vsc_types_len;

        for (p = beg; p < end; p += strlen(p) + 1) {
                if (count >= VS_TYPES_MAX) {
                        mutex_exit(&vscan_svc_mutex);
                        return (-1);
                }

                vscan_svc_types[count] = p;
                ++count;
        }

        mutex_exit(&vscan_svc_cfg_mutex);
        return (0);
}


/*
 * vscan_svc_exempt_file
 *
 * check if a file's size or type exempts it from virus scanning
 *
 * If the file is exempt from virus scanning, allow will be set
 * to define whether files access should be allowed (B_TRUE) or
 * denied (B_FALSE)
 *
 * Returns: 1 exempt
 *          0 scan required
 */
static int
vscan_svc_exempt_file(vnode_t *vp, boolean_t *allow)
{
        struct vattr attr;

        ASSERT(vp != NULL);

        attr.va_mask = AT_SIZE;

        if (VOP_GETATTR(vp, &attr, 0, kcred, NULL) != 0) {
                *allow = B_FALSE;
                return (0);
        }

        mutex_enter(&vscan_svc_cfg_mutex);

        if (attr.va_size > vscan_svc_config.vsc_max_size) {
                DTRACE_PROBE2(vscan__exempt__filesize, char *,
                    vp->v_path, int, *allow);

                *allow = (vscan_svc_config.vsc_allow) ? B_TRUE : B_FALSE;
                mutex_exit(&vscan_svc_cfg_mutex);
                return (1);
        }

        if (vscan_svc_exempt_filetype(vp->v_path)) {
                DTRACE_PROBE1(vscan__exempt__filetype, char *, vp->v_path);
                *allow = B_TRUE;
                mutex_exit(&vscan_svc_cfg_mutex);
                return (1);
        }

        mutex_exit(&vscan_svc_cfg_mutex);
        return (0);
}


/*
 * vscan_svc_exempt_filetype
 *
 * Each entry in vscan_svc_types includes a rule indicator (+,-)
 * followed by the match string for file types to which the rule
 * applies. Look for first match of file type in vscan_svc_types
 * and return 1 (exempt) if the indicator is '-', and 0 (not exempt)
 * if the indicator is '+'.
 * If vscan_svc_match_ext fails, or no match is found, return 0
 * (not exempt)
 *
 * Returns 1: exempt, 0: not exempt
 */
static int
vscan_svc_exempt_filetype(char *filepath)
{
        int i, rc, exempt = 0;
        char *filename, *ext;

        ASSERT(MUTEX_HELD(&vscan_svc_cfg_mutex));

        if ((filename = strrchr(filepath, '/')) == 0)
                filename = filepath;
        else
                filename++;

        if ((ext = strrchr(filename, '.')) == NULL)
                ext = "";
        else
                ext++;

        for (i = 0; i < VS_TYPES_MAX; i ++) {
                if (vscan_svc_types[i] == 0)
                        break;

                rc = vscan_svc_match_ext(vscan_svc_types[i] + 1, ext, 1);
                if (rc == -1)
                        break;
                if (rc > 0) {
                        DTRACE_PROBE2(vscan__type__match, char *, ext,
                            char *, vscan_svc_types[i]);
                        exempt = (vscan_svc_types[i][0] == '-');
                        break;
                }
        }

        return (exempt);
}


/*
 *  vscan_svc_match_ext
 *
 * Performs a case-insensitive match for two strings.  The first string
 * argument can contain the wildcard characters '?' and '*'
 *
 * Returns: 0 no match
 *          1 match
 *         -1 recursion error
 */
static int
vscan_svc_match_ext(char *patn, char *str, int depth)
{
        int c1, c2;
        if (depth > VS_EXT_RECURSE_DEPTH)
                return (-1);

        for (;;) {
                switch (*patn) {
                case 0:
                        return (*str == 0);

                case '?':
                        if (*str != 0) {
                                str++;
                                patn++;
                                continue;
                        }
                        return (0);

                case '*':
                        patn++;
                        if (*patn == 0)
                                return (1);

                        while (*str) {
                                if (vscan_svc_match_ext(patn, str, depth + 1))
                                        return (1);
                                str++;
                        }
                        return (0);

                default:
                        if (*str != *patn) {
                                c1 = *str;
                                c2 = *patn;

                                c1 = tolower(c1);
                                c2 = tolower(c2);
                                if (c1 != c2)
                                        return (0);
                        }
                        str++;
                        patn++;
                        continue;
                }
        }
        /* NOT REACHED */
}


/*
 * vscan_svc_insert_req
 *
 * Insert request in next available available slot in vscan_svc_nodes
 *
 * Returns: idx of slot, or -1 if no slot available
 */
static int
vscan_svc_insert_req(vscan_req_t *req)
{
        int idx;
        vscan_svc_node_t *node;

        ASSERT(MUTEX_HELD(&vscan_svc_mutex));

        if (vscan_svc_counts.vsc_node == vs_nodes_max)
                return (-1);

        for (idx = 1; idx <= vs_nodes_max; idx++) {
                if (vscan_svc_nodes[idx].vsn_req == NULL) {
                        req->vsr_idx = idx;

                        node = &vscan_svc_nodes[idx];
                        (void) memset(node, 0, sizeof (vscan_svc_node_t));
                        node->vsn_req = req;
                        node->vsn_modified = 1;
                        node->vsn_result = VS_STATUS_UNDEFINED;
                        node->vsn_access = VS_ACCESS_UNDEFINED;

                        ++(vscan_svc_counts.vsc_node);
                        return (idx);
                }
        }

        return (-1);
}


/*
 * vscan_svc_remove_req
 */
static void
vscan_svc_remove_req(int idx)
{
        ASSERT(MUTEX_HELD(&vscan_svc_mutex));

        if (idx != 0) {
                (void) memset(&vscan_svc_nodes[idx], 0,
                    sizeof (vscan_svc_node_t));
                --(vscan_svc_counts.vsc_node);
        }
}


/*
 * vscan_svc_reql_find
 */
static vscan_req_t *
vscan_svc_reql_find(vnode_t *vp)
{
        vscan_req_t *req;
        ASSERT(MUTEX_HELD(&vscan_svc_mutex));

        req = list_head(&vscan_svc_reql);

        while (req != NULL) {
                ASSERT(req->vsr_magic == VS_REQ_MAGIC);
                if ((req->vsr_vp == vp) &&
                    (req->vsr_state != VS_SVC_REQ_COMPLETE))
                        break;

                req = list_next(&vscan_svc_reql, req);
        }

        return (req);
}


/*
 * vscan_svc_reql_insert
 */
static vscan_req_t *
vscan_svc_reql_insert(vnode_t *vp)
{
        vscan_req_t *req;

        ASSERT(MUTEX_HELD(&vscan_svc_mutex));

        /* if request already in list then return it */
        if ((req = vscan_svc_reql_find(vp)) != NULL)
                return (req);

        /* if list is full return NULL */
        if (vscan_svc_counts.vsc_reql == vs_reqs_max)
                return (NULL);

        /* create a new request and insert into list */
        VN_HOLD(vp);

        req = kmem_zalloc(sizeof (vscan_req_t), KM_SLEEP);

        req->vsr_magic = VS_REQ_MAGIC;
        if (vscan_svc_seqnum == UINT32_MAX)
                vscan_svc_seqnum = 0;
        req->vsr_seqnum = ++vscan_svc_seqnum;
        req->vsr_vp = vp;
        req->vsr_refcnt = 1; /* decremented in vscan_svc_scan_complete */
        req->vsr_state = VS_SVC_REQ_INIT;
        cv_init(&(req->vsr_cv), NULL, CV_DEFAULT, NULL);

        list_insert_tail(&vscan_svc_reql, req);
        if (vscan_svc_reql_next == NULL)
                vscan_svc_reql_next = req;

        ++(vscan_svc_counts.vsc_reql);

        /* wake reql handler thread */
        cv_signal(&vscan_svc_reql_cv);

        return (req);
}


/*
 * vscan_svc_reql_remove
 */
static void
vscan_svc_reql_remove(vscan_req_t *req)
{
        ASSERT(MUTEX_HELD(&vscan_svc_mutex));
        ASSERT(req->vsr_magic == VS_REQ_MAGIC);

        if (vscan_svc_reql_next == req)
                vscan_svc_reql_next = list_next(&vscan_svc_reql, req);

        list_remove(&vscan_svc_reql, req);
        cv_destroy(&(req->vsr_cv));
        VN_RELE(req->vsr_vp);

        kmem_free(req, sizeof (vscan_req_t));
        --(vscan_svc_counts.vsc_reql);
}