root/usr/src/uts/common/fs/smbclnt/netsmb/smb_conn.c
/*
 * Copyright (c) 2000-2001 Boris Popov
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *    This product includes software developed by Boris Popov.
 * 4. Neither the name of the author nor the names of any co-contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $Id: smb_conn.c,v 1.27.166.1 2005/05/27 02:35:29 lindak Exp $
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
 * Copyright 2024 RackTop Systems, Inc.
 */

/*
 * Connection engine.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kmem.h>
#include <sys/proc.h>
#include <sys/lock.h>
#include <sys/vnode.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/socketvar.h>
#include <sys/cred.h>
#include <netinet/in.h>
#include <inet/ip.h>
#include <inet/ip6.h>
#include <sys/cmn_err.h>
#include <sys/thread.h>
#include <sys/atomic.h>
#include <sys/u8_textprep.h>

#include <netsmb/smb_osdep.h>

#include <netsmb/smb.h>
#include <netsmb/smb2.h>
#include <netsmb/smb_conn.h>
#include <netsmb/smb_subr.h>
#include <netsmb/smb_tran.h>
#include <netsmb/smb_pass.h>

static struct smb_connobj smb_vclist;

void smb_co_init(struct smb_connobj *cp, int level, char *objname);
void smb_co_done(struct smb_connobj *cp);
void smb_co_hold(struct smb_connobj *cp);
void smb_co_rele(struct smb_connobj *cp);
void smb_co_kill(struct smb_connobj *cp);

static void smb_vc_free(struct smb_connobj *cp);
static void smb_vc_gone(struct smb_connobj *cp);

static void smb_share_free(struct smb_connobj *cp);
static void smb_share_gone(struct smb_connobj *cp);

static void smb_fh_free(struct smb_connobj *cp);
static void smb_fh_gone(struct smb_connobj *cp);

int
smb_sm_init(void)
{
        smb_co_init(&smb_vclist, SMBL_SM, "smbsm");
        return (0);
}

int
smb_sm_idle(void)
{
        int error = 0;
        SMB_CO_LOCK(&smb_vclist);
        if (smb_vclist.co_usecount > 1) {
                SMBSDEBUG("%d connections still active\n",
                    smb_vclist.co_usecount - 1);
                error = EBUSY;
        }
        SMB_CO_UNLOCK(&smb_vclist);
        return (error);
}

void
smb_sm_done(void)
{
        /*
         * Why are we not iterating on smb_vclist here?
         * Because the caller has just called smb_sm_idle() to
         * make sure we have no VCs before calling this.
         */
        smb_co_done(&smb_vclist);
}



/*
 * Common code for connection object
 */
/*ARGSUSED*/
void
smb_co_init(struct smb_connobj *cp, int level, char *objname)
{

        mutex_init(&cp->co_lock, objname,  MUTEX_DRIVER, NULL);

        cp->co_level = level;
        cp->co_usecount = 1;
        SLIST_INIT(&cp->co_children);
}

/*
 * Called just before free of an object
 * of which smb_connobj is a part, i.e.
 * _vc_free, _share_free, also sm_done.
 */
void
smb_co_done(struct smb_connobj *cp)
{
        ASSERT(SLIST_EMPTY(&cp->co_children));
        mutex_destroy(&cp->co_lock);
}

static void
smb_co_addchild(
        struct smb_connobj *parent,
        struct smb_connobj *child)
{

        /*
         * Set the child's pointer to the parent.
         * No references yet, so no need to lock.
         */
        ASSERT(child->co_usecount == 1);
        child->co_parent = parent;

        /*
         * Add the child to the parent's list of
         * children, and in-line smb_co_hold
         */
        ASSERT(MUTEX_HELD(&parent->co_lock));
        parent->co_usecount++;
        SLIST_INSERT_HEAD(&parent->co_children, child, co_next);
}

void
smb_co_hold(struct smb_connobj *cp)
{
        SMB_CO_LOCK(cp);
        cp->co_usecount++;
        SMB_CO_UNLOCK(cp);
}

/*
 * Called via smb_vc_rele, smb_share_rele
 */
void
smb_co_rele(struct smb_connobj *co)
{
        struct smb_connobj *parent;
        int old_flags;

        SMB_CO_LOCK(co);

        /*
         * When VC usecount goes from 2 to 1, signal the iod_idle CV.
         * It's unfortunate to have object type-specific logic here,
         * but it's hard to do this anywhere else.
         */
        if (co->co_level == SMBL_VC && co->co_usecount == 2) {
                smb_vc_t *vcp = CPTOVC(co);
                cv_signal(&vcp->iod_idle);
        }
        if (co->co_usecount > 1) {
                co->co_usecount--;
                SMB_CO_UNLOCK(co);
                return;
        }
        ASSERT(co->co_usecount == 1);
        co->co_usecount = 0;

        /*
         * This list of children should be empty now.
         * Check this while we're still linked, so
         * we have a better chance of debugging.
         */
        ASSERT(SLIST_EMPTY(&co->co_children));

        /*
         * OK, this element is going away.
         *
         * We need to drop the lock on this CO so we can take the
         * parent CO lock. The _GONE flag prevents this CO from
         * getting new references before we can unlink it from the
         * parent list.
         *
         * The _GONE flag is also used to ensure that the co_gone
         * function is called only once.  Note that smb_co_kill may
         * do this before we get here.  If we find that the _GONE
         * flag was not already set, then call the co_gone hook
         * (smb_share_gone, smb_vc_gone) which will disconnect
         * the share or the VC, respectively.
         *
         * Note the old: smb_co_gone(co, scred);
         * is now in-line here.
         */
        old_flags = co->co_flags;
        co->co_flags |= SMBO_GONE;
        SMB_CO_UNLOCK(co);

        if ((old_flags & SMBO_GONE) == 0 && co->co_gone)
                co->co_gone(co);

        /*
         * If we have a parent (only smb_vclist does not)
         * then unlink from parent's list of children.
         * We have the only reference to the child.
         */
        parent = co->co_parent;
        if (parent) {
                SMB_CO_LOCK(parent);
                ASSERT(SLIST_FIRST(&parent->co_children));
                if (SLIST_FIRST(&parent->co_children)) {
                        SLIST_REMOVE(&parent->co_children, co,
                            smb_connobj, co_next);
                }
                SMB_CO_UNLOCK(parent);
        }

        /*
         * Now it's safe to free the CO
         */
        if (co->co_free) {
                co->co_free(co);
        }

        /*
         * Finally, if the CO had a parent, decrement
         * the parent's hold count for the lost child.
         */
        if (parent) {
                /*
                 * Recursive call here (easier for debugging).
                 * Can only go two levels.
                 */
                smb_co_rele(parent);
        }
}

/*
 * Do just the first part of what co_gone does,
 * i.e. tree disconnect, or disconnect a VC.
 * This is used to forcibly close things.
 */
void
smb_co_kill(struct smb_connobj *co)
{
        int old_flags;

        SMB_CO_LOCK(co);
        old_flags = co->co_flags;
        co->co_flags |= SMBO_GONE;
        SMB_CO_UNLOCK(co);

        /*
         * Do the same "call only once" logic here as in
         * smb_co_rele, though it's probably not possible
         * for this to be called after smb_co_rele.
         */
        if ((old_flags & SMBO_GONE) == 0 && co->co_gone)
                co->co_gone(co);

        /* XXX: Walk list of children and kill those too? */
}


/*
 * Session objects, which are referred to as "VC" for
 * "virtual cirtuit". This has nothing to do with the
 * CIFS notion of a "virtual cirtuit".  See smb_conn.h
 */

void
smb_vc_hold(struct smb_vc *vcp)
{
        smb_co_hold(VCTOCP(vcp));
}

void
smb_vc_rele(struct smb_vc *vcp)
{
        smb_co_rele(VCTOCP(vcp));
}

void
smb_vc_kill(struct smb_vc *vcp)
{
        smb_co_kill(VCTOCP(vcp));
}

/*
 * Normally called via smb_vc_rele()
 * after co_usecount drops to zero.
 * Also called via: smb_vc_kill()
 *
 * Shutdown the VC to this server,
 * invalidate shares linked with it.
 */
/*ARGSUSED*/
static void
smb_vc_gone(struct smb_connobj *cp)
{
        struct smb_vc *vcp = CPTOVC(cp);

        /*
         * Was smb_vc_disconnect(vcp);
         */
        smb_iod_disconnect(vcp);
}

/*
 * The VC has no more references.  Free it.
 * No locks needed here.
 */
static void
smb_vc_free(struct smb_connobj *cp)
{
        struct smb_vc *vcp = CPTOVC(cp);

        /*
         * The _gone call should have emptied the request list,
         * but let's make sure, as requests may have references
         * to this VC without taking a hold.  (The hold is the
         * responsibility of threads placing requests.)
         */
        ASSERT(vcp->iod_rqlist.tqh_first == NULL);

        if ((vcp->vc_sopt.sv2_capabilities & SMB2_CAP_ENCRYPTION) != 0)
                nsmb_crypt_free_mech(vcp);

        if (vcp->vc_tdata != NULL)
                SMB_TRAN_DONE(vcp);

/*
 * We are not using the iconv routines here. So commenting them for now.
 * REVISIT.
 */
#ifdef NOTYETDEFINED
        if (vcp->vc_tolower)
                iconv_close(vcp->vc_tolower);
        if (vcp->vc_toupper)
                iconv_close(vcp->vc_toupper);
        if (vcp->vc_tolocal)
                iconv_close(vcp->vc_tolocal);
        if (vcp->vc_toserver)
                iconv_close(vcp->vc_toserver);
#endif

        if (vcp->vc_mackey != NULL)
                kmem_free(vcp->vc_mackey, vcp->vc_mackeylen);
        if (vcp->vc_ssnkey != NULL)
                kmem_free(vcp->vc_ssnkey, vcp->vc_ssnkeylen);

        cv_destroy(&vcp->iod_muxwait);
        cv_destroy(&vcp->iod_idle);
        rw_destroy(&vcp->iod_rqlock);
        cv_destroy(&vcp->vc_statechg);
        smb_co_done(VCTOCP(vcp));
        kmem_free(vcp, sizeof (*vcp));
}

/*ARGSUSED*/
int
smb_vc_create(smbioc_ossn_t *ossn, smb_cred_t *scred, smb_vc_t **vcpp)
{
        static char objtype[] = "smb_vc";
        cred_t *cr = scred->scr_cred;
        struct smb_vc *vcp;
        int error = 0;

        ASSERT(MUTEX_HELD(&smb_vclist.co_lock));

        vcp = kmem_zalloc(sizeof (struct smb_vc), KM_SLEEP);

        smb_co_init(VCTOCP(vcp), SMBL_VC, objtype);
        vcp->vc_co.co_free = smb_vc_free;
        vcp->vc_co.co_gone = smb_vc_gone;

        cv_init(&vcp->vc_statechg, objtype, CV_DRIVER, NULL);
        rw_init(&vcp->iod_rqlock, objtype, RW_DRIVER, NULL);
        cv_init(&vcp->iod_idle, objtype, CV_DRIVER, NULL);
        cv_init(&vcp->iod_muxwait, objtype, CV_DRIVER, NULL);

        /* Expanded TAILQ_HEAD_INITIALIZER */
        vcp->iod_rqlist.tqh_last = &vcp->iod_rqlist.tqh_first;

        /* A brand new VC should connect. */
        vcp->vc_state = SMBIOD_ST_RECONNECT;

        /*
         * These identify the connection.
         */
        vcp->vc_zoneid = getzoneid();
        bcopy(ossn, &vcp->vc_ssn, sizeof (*ossn));

        /* This fills in vcp->vc_tdata */
        vcp->vc_tdesc = &smb_tran_nbtcp_desc;
        if ((error = SMB_TRAN_CREATE(vcp, cr)) != 0)
                goto errout;

        /* Success! */
        smb_co_addchild(&smb_vclist, VCTOCP(vcp));
        *vcpp = vcp;
        return (0);

errout:
        /*
         * This will destroy the new vc.
         * See: smb_vc_free
         */
        smb_vc_rele(vcp);
        return (error);
}

/*
 * Find or create a VC identified by the info in ossn
 * and return it with a "hold", but not locked.
 */
/*ARGSUSED*/
int
smb_vc_findcreate(smbioc_ossn_t *ossn, smb_cred_t *scred, smb_vc_t **vcpp)
{
        struct smb_connobj *co;
        struct smb_vc *vcp;
        smbioc_ssn_ident_t *vc_id;
        int error;
        zoneid_t zoneid = getzoneid();

        *vcpp = vcp = NULL;

        SMB_CO_LOCK(&smb_vclist);

        /* var, head, next_field */
        SLIST_FOREACH(co, &smb_vclist.co_children, co_next) {
                vcp = CPTOVC(co);

                /*
                 * Some things we can check without
                 * holding the lock (those that are
                 * set at creation and never change).
                 */

                /* VCs in other zones are invisibile. */
                if (vcp->vc_zoneid != zoneid)
                        continue;

                /* Also segregate by Unix owner. */
                if (vcp->vc_owner != ossn->ssn_owner)
                        continue;

                /*
                 * Compare identifying info:
                 * server address, user, domain
                 * names are case-insensitive
                 */
                vc_id = &vcp->vc_ssn.ssn_id;
                if (bcmp(&vc_id->id_srvaddr,
                    &ossn->ssn_id.id_srvaddr,
                    sizeof (vc_id->id_srvaddr)))
                        continue;
                if (u8_strcmp(vc_id->id_user, ossn->ssn_id.id_user, 0,
                    U8_STRCMP_CI_LOWER, U8_UNICODE_LATEST, &error))
                        continue;
                if (u8_strcmp(vc_id->id_domain, ossn->ssn_id.id_domain, 0,
                    U8_STRCMP_CI_LOWER, U8_UNICODE_LATEST, &error))
                        continue;

                /*
                 * We have a match, but still have to check
                 * the _GONE flag, and do that with a lock.
                 * No new references when _GONE is set.
                 *
                 * Also clear SMBVOPT_CREATE which the caller
                 * may check to find out if we did create.
                 */
                SMB_VC_LOCK(vcp);
                if ((vcp->vc_flags & SMBV_GONE) == 0) {
                        ossn->ssn_vopt &= ~SMBVOPT_CREATE;
                        /*
                         * Return it held, unlocked.
                         * In-line smb_vc_hold here.
                         */
                        co->co_usecount++;
                        SMB_VC_UNLOCK(vcp);
                        *vcpp = vcp;
                        error = 0;
                        goto out;
                }
                SMB_VC_UNLOCK(vcp);
                /* keep looking. */
        }
        vcp = NULL;

        /* Note: smb_vclist is still locked. */

        if (ossn->ssn_vopt & SMBVOPT_CREATE) {
                /*
                 * Create a new VC.  It starts out with
                 * hold count = 1, so don't incr. here.
                 */
                error = smb_vc_create(ossn, scred, &vcp);
                if (error == 0)
                        *vcpp = vcp;
        } else
                error = ENOENT;

out:
        SMB_CO_UNLOCK(&smb_vclist);
        return (error);
}


/*
 * Helper functions that operate on VCs
 */

/*
 * Get a pointer to the IP address suitable for passing to Trusted
 * Extensions find_tpc() routine.  Used by smbfs_mount_label_policy().
 * Compare this code to nfs_mount_label_policy() if problems arise.
 */
void *
smb_vc_getipaddr(struct smb_vc *vcp, int *ipvers)
{
        smbioc_ssn_ident_t *id = &vcp->vc_ssn.ssn_id;
        void *ret;

        switch (id->id_srvaddr.sa.sa_family) {
        case AF_INET:
                *ipvers = IPV4_VERSION;
                ret = &id->id_srvaddr.sin.sin_addr;
                break;

        case AF_INET6:
                *ipvers = IPV6_VERSION;
                ret = &id->id_srvaddr.sin6.sin6_addr;
                break;
        default:
                SMBSDEBUG("invalid address family %d\n",
                    id->id_srvaddr.sa.sa_family);
                *ipvers = 0;
                ret = NULL;
                break;
        }
        return (ret);
}

void
smb_vc_walkshares(struct smb_vc *vcp,
        walk_share_func_t func)
{
        smb_connobj_t *co;
        smb_share_t *ssp;

        /*
         * Walk the share list calling func(ssp, arg)
         */
        SMB_VC_LOCK(vcp);
        SLIST_FOREACH(co, &(VCTOCP(vcp)->co_children), co_next) {
                ssp = CPTOSS(co);
                SMB_SS_LOCK(ssp);
                func(ssp);
                SMB_SS_UNLOCK(ssp);
        }
        SMB_VC_UNLOCK(vcp);
}


/*
 * Share implementation
 */

void
smb_share_hold(struct smb_share *ssp)
{
        smb_co_hold(SSTOCP(ssp));
}

void
smb_share_rele(struct smb_share *ssp)
{
        smb_co_rele(SSTOCP(ssp));
}

void
smb_share_kill(struct smb_share *ssp)
{
        smb_co_kill(SSTOCP(ssp));
}

/*
 * Normally called via smb_share_rele()
 * after co_usecount drops to zero.
 * Also called via: smb_share_kill()
 */
static void
smb_share_gone(struct smb_connobj *cp)
{
        struct smb_cred scred;
        struct smb_share *ssp = CPTOSS(cp);
        smb_vc_t *vcp = SSTOVC(ssp);

        smb_credinit(&scred, NULL);
        smb_iod_shutdown_share(ssp);
        if (vcp->vc_flags & SMBV_SMB2)
                (void) smb2_smb_treedisconnect(ssp, &scred);
        else
                (void) smb_smb_treedisconnect(ssp, &scred);
        smb_credrele(&scred);
}

/*
 * Normally called via smb_share_rele()
 * after co_usecount drops to zero.
 */
static void
smb_share_free(struct smb_connobj *cp)
{
        struct smb_share *ssp = CPTOSS(cp);

        cv_destroy(&ssp->ss_conn_done);
        smb_co_done(SSTOCP(ssp));
        kmem_free(ssp, sizeof (*ssp));
}

/*
 * Allocate share structure and attach it to the given VC
 * Connection expected to be locked on entry. Share will be returned
 * in locked state.
 */
/*ARGSUSED*/
int
smb_share_create(smbioc_tcon_t *tcon, struct smb_vc *vcp,
        struct smb_share **sspp, struct smb_cred *scred)
{
        static char objtype[] = "smb_ss";
        struct smb_share *ssp;

        ASSERT(MUTEX_HELD(&vcp->vc_lock));

        ssp = kmem_zalloc(sizeof (struct smb_share), KM_SLEEP);
        smb_co_init(SSTOCP(ssp), SMBL_SHARE, objtype);
        ssp->ss_co.co_free = smb_share_free;
        ssp->ss_co.co_gone = smb_share_gone;

        cv_init(&ssp->ss_conn_done, objtype, CV_DRIVER, NULL);
        ssp->ss_tid = SMB_TID_UNKNOWN;
        ssp->ss2_tree_id = SMB2_TID_UNKNOWN;

        bcopy(&tcon->tc_sh, &ssp->ss_ioc,
            sizeof (smbioc_oshare_t));

        smb_co_addchild(VCTOCP(vcp), SSTOCP(ssp));
        *sspp = ssp;

        return (0);
}

/*
 * Find or create a share under the given VC
 * and return it with a "hold", but not locked.
 */

int
smb_share_findcreate(smbioc_tcon_t *tcon, struct smb_vc *vcp,
        struct smb_share **sspp, struct smb_cred *scred)
{
        struct smb_connobj *co;
        struct smb_share *ssp = NULL;
        int error = 0;

        *sspp = NULL;

        SMB_VC_LOCK(vcp);

        /* var, head, next_field */
        SLIST_FOREACH(co, &(VCTOCP(vcp)->co_children), co_next) {
                ssp = CPTOSS(co);

                /* Share name */
                if (u8_strcmp(ssp->ss_name, tcon->tc_sh.sh_name, 0,
                    U8_STRCMP_CI_LOWER, U8_UNICODE_LATEST, &error))
                        continue;

                /*
                 * We have a match, but still have to check
                 * the _GONE flag, and do that with a lock.
                 * No new references when _GONE is set.
                 *
                 * Also clear SMBSOPT_CREATE which the caller
                 * may check to find out if we did create.
                 */
                SMB_SS_LOCK(ssp);
                if ((ssp->ss_flags & SMBS_GONE) == 0) {
                        tcon->tc_opt &= ~SMBSOPT_CREATE;
                        /*
                         * Return it held, unlocked.
                         * In-line smb_share_hold here.
                         */
                        co->co_usecount++;
                        SMB_SS_UNLOCK(ssp);
                        *sspp = ssp;
                        error = 0;
                        goto out;
                }
                SMB_SS_UNLOCK(ssp);
                /* keep looking. */
        }
        ssp = NULL;

        /* Note: vcp (list of shares) is still locked. */

        if (tcon->tc_opt & SMBSOPT_CREATE) {
                /*
                 * Create a new share.  It starts out with
                 * hold count = 1, so don't incr. here.
                 */
                error = smb_share_create(tcon, vcp, &ssp, scred);
                if (error == 0)
                        *sspp = ssp;
        } else
                error = ENOENT;

out:
        SMB_VC_UNLOCK(vcp);
        return (error);
}


/*
 * Helper functions that operate on shares
 */

/*
 * Mark this share as invalid, so consumers will know
 * their file handles have become invalid.
 *
 * Most share consumers store a copy of ss_vcgenid when
 * opening a file handle and compare that with what's in
 * the share before using a file handle.  If the genid
 * doesn't match, the file handle has become "stale"
 * due to disconnect.  Therefore, zap ss_vcgenid here.
 */
void
smb_share_invalidate(struct smb_share *ssp)
{

        ASSERT(MUTEX_HELD(&ssp->ss_lock));

        ssp->ss_flags &= ~SMBS_CONNECTED;
        ssp->ss_tid = SMB_TID_UNKNOWN;
        ssp->ss_vcgenid = 0;
}

/*
 * Connect (or reconnect) a share object.
 *
 * Called by smb_usr_get_tree() for new connections,
 * and called by smb_rq_enqueue() for reconnect.
 */
int
smb_share_tcon(smb_share_t *ssp, smb_cred_t *scred)
{
        smb_vc_t *vcp = SSTOVC(ssp);
        clock_t tmo;
        int error;

        SMB_SS_LOCK(ssp);

        if (ssp->ss_flags & SMBS_CONNECTED) {
                SMBIODEBUG("alread connected?");
                error = 0;
                goto out;
        }

        /*
         * Wait for completion of any state changes
         * that might be underway.
         */
        while (ssp->ss_flags & SMBS_RECONNECTING) {
                ssp->ss_conn_waiters++;
                tmo = cv_wait_sig(&ssp->ss_conn_done, &ssp->ss_lock);
                ssp->ss_conn_waiters--;
                if (tmo == 0) {
                        /* Interrupt! */
                        error = EINTR;
                        goto out;
                }
        }

        /* Did someone else do it for us? */
        if (ssp->ss_flags & SMBS_CONNECTED) {
                error = 0;
                goto out;
        }

        /*
         * OK, we'll do the work.
         */
        ssp->ss_flags |= SMBS_RECONNECTING;

        /*
         * Drop the lock while doing the TCON.
         * On success, sets ss_tid, ss_vcgenid,
         * and ss_flags |= SMBS_CONNECTED;
         */
        SMB_SS_UNLOCK(ssp);
        if (vcp->vc_flags & SMBV_SMB2)
                error = smb2_smb_treeconnect(ssp, scred);
        else
                error = smb_smb_treeconnect(ssp, scred);
        SMB_SS_LOCK(ssp);

        ssp->ss_flags &= ~SMBS_RECONNECTING;

        /* They can all go ahead! */
        if (ssp->ss_conn_waiters)
                cv_broadcast(&ssp->ss_conn_done);

out:
        SMB_SS_UNLOCK(ssp);

        return (error);
}

/*
 * File handle level functions
 */

void
smb_fh_hold(struct smb_fh *fhp)
{
        smb_co_hold(FHTOCP(fhp));
}

void
smb_fh_rele(struct smb_fh *fhp)
{
        smb_co_rele(FHTOCP(fhp));
}

void
smb_fh_close(struct smb_fh *fhp)
{
        smb_co_kill(FHTOCP(fhp));
}

/*
 * Normally called via smb_fh_rele()
 * after co_usecount drops to zero.
 * Also called via: smb_fh_kill()
 */
static void
smb_fh_gone(struct smb_connobj *cp)
{
        struct smb_cred scred;
        struct smb_fh *fhp = CPTOFH(cp);
        smb_share_t *ssp = FHTOSS(fhp);
        int err;

        if ((fhp->fh_flags & SMBFH_VALID) == 0)
                return;

        /*
         * We have no durable handles (yet) so if there has been a
         * reconnect, don't bother to close this handle.
         */
        if (fhp->fh_vcgenid != ssp->ss_vcgenid)
                return;

        smb_credinit(&scred, NULL);
        err = smb_smb_close(ssp, fhp, &scred);
        smb_credrele(&scred);
        if (err) {
                SMBSDEBUG("close err=%d\n", err);
        }
}

/*
 * Normally called via smb_fh_rele()
 * after co_usecount drops to zero.
 */
static void
smb_fh_free(struct smb_connobj *cp)
{
        struct smb_fh *fhp = CPTOFH(cp);

        smb_co_done(FHTOCP(fhp));
        kmem_free(fhp, sizeof (*fhp));
}

/*
 * Allocate fh structure and attach it to the given share.
 * Share expected to be locked on entry.
 */
/*ARGSUSED*/
int
smb_fh_create(smb_share_t *ssp, struct smb_fh **fhpp)
{
        static char objtype[] = "smb_fh";
        struct smb_fh *fhp;

        fhp = kmem_zalloc(sizeof (struct smb_fh), KM_SLEEP);
        smb_co_init(FHTOCP(fhp), SMBL_FH, objtype);
        fhp->fh_co.co_free = smb_fh_free;
        fhp->fh_co.co_gone = smb_fh_gone;

        SMB_SS_LOCK(ssp);
        if ((ssp->ss_flags & SMBS_GONE) != 0) {
                SMB_SS_UNLOCK(ssp);
                smb_fh_free(FHTOCP(fhp));
                return (ENOTCONN);
        }

        smb_co_addchild(SSTOCP(ssp), FHTOCP(fhp));
        *fhpp = fhp;
        SMB_SS_UNLOCK(ssp);

        return (0);
}

void
smb_fh_opened(struct smb_fh *fhp)
{
        smb_share_t *ssp = FHTOSS(fhp);

        SMB_FH_LOCK(fhp);
        fhp->fh_vcgenid = ssp->ss_vcgenid;
        fhp->fh_flags |= SMBFH_VALID;
        SMB_FH_UNLOCK(fhp);
}


/*
 * Solaris zones support
 */
/*ARGSUSED*/
void
lingering_vc(struct smb_vc *vc)
{
        /* good place for a breakpoint */
        DEBUG_ENTER("lingering VC");
}

/*
 * On zone shutdown, kill any IOD threads still running in this zone.
 */
/* ARGSUSED */
void
nsmb_zone_shutdown(zoneid_t zoneid, void *data)
{
        struct smb_connobj *co;
        struct smb_vc *vcp;

        SMB_CO_LOCK(&smb_vclist);
        SLIST_FOREACH(co, &smb_vclist.co_children, co_next) {
                vcp = CPTOVC(co);

                if (vcp->vc_zoneid != zoneid)
                        continue;

                /*
                 * This will close the connection, and
                 * cause the IOD thread to terminate.
                 */
                smb_vc_kill(vcp);
        }
        SMB_CO_UNLOCK(&smb_vclist);
}

/*
 * On zone destroy, kill any IOD threads and free all resources they used.
 */
/* ARGSUSED */
void
nsmb_zone_destroy(zoneid_t zoneid, void *data)
{
        struct smb_connobj *co;
        struct smb_vc *vcp;

        /*
         * We will repeat what should have already happened
         * in zone_shutdown to make things go away.
         *
         * There should have been an smb_vc_rele call
         * by now for all VCs in the zone.  If not,
         * there's probably more we needed to do in
         * the shutdown call.
         */

        SMB_CO_LOCK(&smb_vclist);

        if (smb_vclist.co_usecount > 1) {
                SMBERROR("%d connections still active\n",
                    smb_vclist.co_usecount - 1);
        }

        /* var, head, next_field */
        SLIST_FOREACH(co, &smb_vclist.co_children, co_next) {
                vcp = CPTOVC(co);

                if (vcp->vc_zoneid != zoneid)
                        continue;

                /* Debugging */
                lingering_vc(vcp);
        }

        SMB_CO_UNLOCK(&smb_vclist);
}