/* * 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. */ /* * Connection engine. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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); 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) { /* * XXX Q4BP 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); 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_tdata) 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); cv_destroy(&vcp->iod_idle); rw_destroy(&vcp->iod_rqlock); sema_destroy(&vcp->vc_sendlock); 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); sema_init(&vcp->vc_sendlock, 1, objtype, SEMA_DRIVER, NULL); rw_init(&vcp->iod_rqlock, objtype, RW_DRIVER, NULL); cv_init(&vcp->iod_idle, objtype, CV_DRIVER, NULL); /* Expanded TAILQ_HEAD_INITIALIZER */ vcp->iod_rqlist.tqh_last = &vcp->iod_rqlist.tqh_first; vcp->vc_state = SMBIOD_ST_IDLE; /* * 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_credinit(&scred, NULL); smb_iod_shutdown_share(ssp); (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; 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) { 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); 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); } /* * 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); }