#include <sys/sysctl.h>
#include <sys/smb_apple.h>
#include <sys/kauth.h>
#include <netsmb/smb.h>
#include <netsmb/smb_subr.h>
#include <netsmb/smb_conn.h>
#include <netsmb/smb_dev.h>
#include <netsmb/smb_tran.h>
#include <netsmb/smb_trantcp.h>
#include <netsmb/smb_gss.h>
static struct smb_connobj smb_vclist;
static int smb_vcnext = 1;
extern struct linker_set sysctl_net_smb;
SYSCTL_DECL(_net_smb);
SYSCTL_NODE(_net, OID_AUTO, smb, CTLFLAG_RW, NULL, "SMB protocol");
static void smb_co_put(struct smb_connobj *cp, vfs_context_t context);
static int smb_co_lock(struct smb_connobj *cp)
{
if (cp->co_flags & SMBO_GONE)
return EBUSY;
if (cp->co_lockowner == current_thread()) {
cp->co_lockcount++;
} else {
lck_mtx_lock(cp->co_lock);
if (cp->co_flags & SMBO_GONE) {
lck_mtx_unlock(cp->co_lock);
return EBUSY;
}
cp->co_lockowner = current_thread();
cp->co_lockcount = 1;
}
return (0);
}
static void smb_co_unlock(struct smb_connobj *cp)
{
if (cp->co_lockowner && (cp->co_lockowner != current_thread())) {
SMBERROR("not owner of lock");
} else if (cp->co_lockcount && (--cp->co_lockcount == 0)) {
cp->co_lockowner = NULL;
lck_mtx_unlock(cp->co_lock);
lck_mtx_lock(&(cp)->co_interlock);
if (cp->co_lock_flags & SMBFS_CO_LOCK_WAIT){
cp->co_lock_flags &= ~SMBFS_CO_LOCK_WAIT;
lck_mtx_unlock(&(cp)->co_interlock);
wakeup(&cp->co_lock);
} else
lck_mtx_unlock(&(cp)->co_interlock);
}
}
static void
smb_co_init(struct smb_connobj *cp, int level, const char *objname, struct proc *p)
{
#pragma unused (objname, p)
SLIST_INIT(&cp->co_children);
lck_mtx_init(&cp->co_interlock, co_lck_group, co_lck_attr);
cp->co_lock = lck_mtx_alloc_init(co_lck_group, co_lck_attr);
cp->co_lock_flags = 0;
cp->co_lockowner = 0;
cp->co_lockcount = 0;
cp->co_level = level;
cp->co_usecount = 1;
KASSERT(smb_co_lock(cp) == 0,
("smb_co_init: lock failed"));
}
static void smb_co_done(struct smb_connobj *cp)
{
lck_mtx_destroy(&cp->co_interlock, co_lck_group);
lck_mtx_free(cp->co_lock, co_lck_group);
cp->co_lock = 0;
cp->co_lock_flags = 0;
cp->co_lockowner = 0;
cp->co_lockcount = 0;
}
static void smb_co_gone(struct smb_connobj *cp, vfs_context_t context)
{
struct smb_connobj *parent;
lck_mtx_lock(&(cp)->co_interlock);
while (cp->co_lockcount > 0) {
cp->co_lock_flags |= SMBFS_CO_LOCK_WAIT;
msleep(&cp->co_lock, &(cp)->co_interlock, 0, 0, 0);
}
lck_mtx_unlock(&(cp)->co_interlock);
if (cp->co_gone)
cp->co_gone(cp, context);
parent = cp->co_parent;
if (parent) {
if (smb_co_lock(parent)) {
SMBERROR("unable to lock level %d\n", parent->co_level);
} else {
SLIST_REMOVE(&parent->co_children, cp, smb_connobj,
co_next);
smb_co_put(parent, context);
}
}
if (cp->co_free)
cp->co_free(cp);
}
static void smb_co_put(struct smb_connobj *cp, vfs_context_t context)
{
lck_mtx_lock(&(cp)->co_interlock);
if (cp->co_usecount > 1) {
cp->co_usecount--;
} else if (cp->co_usecount == 1) {
cp->co_usecount--;
cp->co_flags |= SMBO_GONE;
} else {
SMBERROR("negative usecount\n");
}
lck_mtx_unlock(&(cp)->co_interlock);
smb_co_unlock(cp);
if ((cp->co_flags & SMBO_GONE) == 0)
return;
smb_co_gone(cp, context);
}
static void smb_co_ref(struct smb_connobj *cp)
{
lck_mtx_lock(&(cp)->co_interlock);
cp->co_usecount++;
lck_mtx_unlock(&(cp)->co_interlock);
}
static void smb_co_addchild(struct smb_connobj *parent, struct smb_connobj *child)
{
smb_co_ref(parent);
SLIST_INSERT_HEAD(&parent->co_children, child, co_next);
child->co_parent = parent;
}
static void smb_co_rele(struct smb_connobj *cp, vfs_context_t context)
{
lck_mtx_lock(&(cp)->co_interlock);
if (cp->co_usecount > 1) {
cp->co_usecount--;
lck_mtx_unlock(&(cp)->co_interlock);
return;
}
if (cp->co_usecount == 0) {
SMBERROR("negative co_usecount for level %d\n", cp->co_level);
lck_mtx_unlock(&(cp)->co_interlock);
return;
}
cp->co_usecount--;
if (cp->co_flags & SMBO_GONE) {
lck_mtx_unlock(&(cp)->co_interlock);
return;
}
cp->co_flags |= SMBO_GONE;
lck_mtx_unlock(&(cp)->co_interlock);
smb_co_gone(cp, context);
}
struct sockaddr *
smb_dup_sockaddr(struct sockaddr *sa, int canwait)
{
struct sockaddr *sa2;
MALLOC(sa2, struct sockaddr *, sa->sa_len, M_SONAME,
canwait ? M_WAITOK : M_NOWAIT);
if (sa2)
bcopy(sa, sa2, sa->sa_len);
return (sa2);
}
int smb_sm_init(void)
{
smb_co_init(&smb_vclist, SMBL_SM, "smbsm", current_proc());
smb_co_unlock(&smb_vclist);
return (0);
}
int smb_sm_done(void)
{
if (smb_vclist.co_usecount > 1) {
SMBERROR("%d connections still active\n", smb_vclist.co_usecount - 1);
return (EBUSY);
}
smb_co_done(&smb_vclist);
return (0);
}
static void smb_sm_lockvclist()
{
KASSERT((smb_co_lock(&smb_vclist) == 0), ("smb_sm_lockvclist: lock failed"));
}
static void smb_sm_unlockvclist()
{
smb_co_unlock(&smb_vclist);
}
void smb_vc_reset(struct smb_vc *vcp)
{
vcp->vc_hflags2 &= (SMB_FLAGS2_EXT_SEC | SMB_FLAGS2_KNOWS_LONG_NAMES | SMB_FLAGS2_UNICODE);
vcp->vc_mid = 0;
vcp->vc_number = smb_vcnext++;
smb_reset_sig(vcp);
}
void smb_vc_ref(struct smb_vc *vcp)
{
smb_co_ref(VCTOCP(vcp));
}
void smb_vc_rele(struct smb_vc *vcp, vfs_context_t context)
{
smb_co_rele(VCTOCP(vcp), context);
}
static void smb_vc_put(struct smb_vc *vcp, vfs_context_t context)
{
smb_co_put(VCTOCP(vcp), context);
}
int smb_vc_lock(struct smb_vc *vcp)
{
return smb_co_lock(VCTOCP(vcp));
}
void smb_vc_unlock(struct smb_vc *vcp)
{
smb_co_unlock(VCTOCP(vcp));
}
static void smb_vc_free(struct smb_connobj *cp)
{
struct smb_vc *vcp = CPTOVC(cp);
if (vcp->vc_iod)
smb_iod_destroy(vcp->vc_iod);
vcp->vc_iod = NULL;
SMB_STRFREE(vcp->NativeOS);
SMB_STRFREE(vcp->NativeLANManager);
SMB_STRFREE(vcp->vc_username);
SMB_STRFREE(vcp->vc_uppercase_username);
SMB_STRFREE(vcp->vc_srvname);
SMB_STRFREE(vcp->vc_localname);
SMB_STRFREE(vcp->vc_pass);
SMB_STRFREE(vcp->vc_domain);
if (vcp->vc_mackey)
free(vcp->vc_mackey, M_SMBTEMP);
if (vcp->vc_saddr)
free(vcp->vc_saddr, M_SONAME);
if (vcp->vc_laddr)
free(vcp->vc_laddr, M_SONAME);
smb_gss_destroy(&vcp->vc_gss);
if (vcp->throttle_info)
throttle_info_release(vcp->throttle_info);
vcp->throttle_info = NULL;
smb_co_done(VCTOCP(vcp));
lck_mtx_destroy(&vcp->vc_stlock, vcst_lck_group);
free(vcp, M_SMBCONN);
}
static int smb_vc_disconnect(struct smb_vc *vcp)
{
if (vcp->vc_iod)
smb_iod_request(vcp->vc_iod, SMBIOD_EV_DISCONNECT | SMBIOD_EV_SYNC, NULL);
return (0);
}
static void smb_vc_gone(struct smb_connobj *cp, vfs_context_t context)
{
#pragma unused(context)
struct smb_vc *vcp = CPTOVC(cp);
smb_vc_disconnect(vcp);
}
static int smb_vc_create(struct smbioc_negotiate *vcspec,
struct sockaddr *saddr, struct sockaddr *laddr,
vfs_context_t context, struct smb_vc **vcpp)
{
struct smb_vc *vcp;
int error;
vcp = smb_zmalloc(sizeof(*vcp), M_SMBCONN, M_WAITOK);
smb_co_init(VCTOCP(vcp), SMBL_VC, "smb_vc", vfs_context_proc(context));
vcp->obj.co_free = smb_vc_free;
vcp->obj.co_gone = smb_vc_gone;
vcp->vc_number = smb_vcnext++;
vcp->vc_timo = SMB_DEFRQTIMO;
vcp->vc_smbuid = SMB_UID_UNKNOWN;
vcp->vc_tdesc = &smb_tran_nbtcp_desc;
vcp->vc_seqno = 0;
vcp->vc_mackey = NULL;
vcp->vc_mackeylen = 0;
vcp->vc_saddr = saddr;
vcp->vc_laddr = laddr;
vcp->vc_flags &= ~SMBV_USER_LAND_MASK;
vcp->vc_flags |= (vcspec->ioc_ssn.ioc_opt & SMBV_USER_LAND_MASK);
vcp->throttle_info = throttle_info_create();
#ifdef DEBUG_TURN_OFF_EXT_SEC
vcp->vc_hflags2 = SMB_FLAGS2_KNOWS_LONG_NAMES;
#else // DEBUG_TURN_OFF_EXT_SEC
vcp->vc_hflags2 = SMB_FLAGS2_KNOWS_LONG_NAMES | SMB_FLAGS2_EXT_SEC | SMB_FLAGS2_UNICODE;
#endif // DEBUG_TURN_OFF_EXT_SEC
vcp->vc_uid = vcspec->ioc_ssn.ioc_owner;
vcp->reconnect_wait_time = vcspec->ioc_ssn.ioc_reconnect_wait_time;
lck_mtx_init(&vcp->vc_stlock, vcst_lck_group, vcst_lck_attr);
error = 0;
itry {
ierror((vcp->vc_srvname = smb_strdup(vcspec->ioc_ssn.ioc_srvname,
sizeof(vcspec->ioc_ssn.ioc_srvname))) == NULL, ENOMEM);
ierror((vcp->vc_localname = smb_strdup(vcspec->ioc_ssn.ioc_localname,
sizeof(vcspec->ioc_ssn.ioc_localname))) == NULL, ENOMEM);
ithrow(smb_iod_create(vcp));
*vcpp = vcp;
smb_sm_lockvclist();
smb_co_addchild(&smb_vclist, VCTOCP(vcp));
smb_sm_unlockvclist();
} icatch(error) {
smb_vc_put(vcp, context);
} ifinally {
} iendtry;
return error;
}
static int smb_sm_lookupint(struct sockaddr *sap, uid_t owner, char *username,
u_int32_t user_flags, struct smb_vc **vcpp)
{
struct smb_vc *vcp;
int error;
DBG_ASSERT(vcpp);
tryagain:
smb_sm_lockvclist();
error = ENOENT;
SMBCO_FOREACH(vcp, &smb_vclist) {
if (*vcpp && vcp != *vcpp)
continue;
else if (*vcpp) {
error = smb_vc_lock(vcp);
DBG_ASSERT(error == 0);
break;
} else {
DBG_ASSERT(sap);
if ((vcp->vc_flags & SMBV_AUTH_DONE) != SMBV_AUTH_DONE) {
continue;
}
if (!CONNADDREQ(vcp->vc_saddr, sap)) {
continue;
}
if (vcp->vc_uid != owner) {
continue;
}
if (vcp->vc_flags & SMBV_PRIVATE_VC) {
continue;
}
error = smb_vc_lock(vcp);
if (error) {
smb_sm_unlockvclist();
goto tryagain;
}
if (user_flags & SMBV_KERBEROS_ACCESS) {
if (vcp->vc_flags & SMBV_KERBEROS_ACCESS) {
error = 0;
break;
} else {
smb_vc_unlock(vcp);
error = ENOENT;
continue;
}
}
if (user_flags & SMBV_GUEST_ACCESS) {
if (vcp->vc_flags & SMBV_GUEST_ACCESS) {
error = 0;
break;
} else {
smb_vc_unlock(vcp);
error = ENOENT;
continue;
}
}
if (username && username[0]) {
if (vcp->vc_username &&
((strncmp(vcp->vc_username, username, SMB_MAXUSERNAMELEN + 1)) == 0)) {
error = 0;
break;
} else {
smb_vc_unlock(vcp);
error = ENOENT;
continue;
}
}
error = 0;
break;
}
}
if (vcp && !error) {
smb_vc_ref(vcp);
*vcpp = vcp;
}
smb_sm_unlockvclist();
return error;
}
int smb_sm_negotiate(struct smbioc_negotiate *vcspec, vfs_context_t context,
struct smb_vc **vcpp, struct smb_dev *sdp)
{
struct smb_vc *vcp = NULL;
struct sockaddr *saddr, *laddr;
int error;
saddr = smb_memdupin(vcspec->ioc_kern_saddr, vcspec->ioc_saddr_len);
if (saddr == NULL) {
return ENOMEM;
}
laddr = smb_memdupin(vcspec->ioc_kern_laddr, vcspec->ioc_laddr_len);
if (laddr == NULL) {
free(saddr, M_SMBDATA);
return ENOMEM;
}
*vcpp = vcp = NULL;
if ((vcspec->ioc_ssn.ioc_opt & SMBV_PRIVATE_VC) != SMBV_PRIVATE_VC) {
error = smb_sm_lookupint(saddr, vcspec->ioc_ssn.ioc_owner,
vcspec->ioc_user, vcspec->ioc_ssn.ioc_opt, &vcp);
}
else
error = ENOENT;
if (error == 0) {
free(saddr, M_SMBDATA);
free(laddr, M_SMBDATA);
vcspec->ioc_extra_flags |= SMB_SHARING_VC;
} else {
error = smb_vc_create(vcspec, saddr, laddr, context, &vcp);
if (error == 0) {
if (vcspec->ioc_extra_flags & TRY_BOTH_PORTS)
sdp->sd_flags |= NSMBFL_TRYBOTH;
vcp->connect_flag = &sdp->sd_flags;
error = smb_vc_negotiate(vcp, context);
vcp->connect_flag = NULL;
sdp->sd_flags &= ~NSMBFL_TRYBOTH;
if (error)
smb_vc_put(vcp, context);
}
}
if ((error == 0) && (vcp)) {
*vcpp = vcp;
smb_vc_unlock(vcp);
}
return error;
}
int smb_sm_ssnsetup(struct smb_vc *vcp, struct smbioc_setup *sspec,
vfs_context_t context)
{
int error;
error = smb_sm_lookupint(NULL, 0, NULL, 0, &vcp);
if (error) {
SMBERROR("The virtual circtuit was not found: error = %d\n", error);
return error;
}
if ((vcp->vc_flags & SMBV_AUTH_DONE) == SMBV_AUTH_DONE)
goto done;
vcp->vc_flags &= ~SMBV_USER_LAND_MASK;
vcp->vc_flags |= (sspec->ioc_vcflags & SMBV_USER_LAND_MASK);
SMB_STRFREE(vcp->vc_username);
SMB_STRFREE(vcp->vc_uppercase_username);
SMB_STRFREE(vcp->vc_pass);
SMB_STRFREE(vcp->vc_domain);
SMB_STRFREE(vcp->vc_gss.gss_cpn);
SMB_STRFREE(vcp->vc_gss.gss_spn);
vcp->vc_username = smb_strdup(sspec->ioc_user, sizeof(sspec->ioc_user));
vcp->vc_uppercase_username = smb_strdup(sspec->ioc_uppercase_user, sizeof(sspec->ioc_uppercase_user));
vcp->vc_pass = smb_strdup(sspec->ioc_password, sizeof(sspec->ioc_password));
vcp->vc_domain = smb_strdup(sspec->ioc_domain, sizeof(sspec->ioc_domain));
if ((vcp->vc_pass == NULL) || (vcp->vc_domain == NULL) ||
(vcp->vc_username == NULL)) {
error = ENOMEM;
goto done;
}
if (sspec->ioc_kclientpn[0])
vcp->vc_gss.gss_cpn = smb_strdup(sspec->ioc_kclientpn, sizeof(sspec->ioc_kclientpn));
if (sspec->ioc_kservicepn[0])
vcp->vc_gss.gss_spn = smb_strdup(sspec->ioc_kservicepn, sizeof(sspec->ioc_kservicepn));
error = smb_vc_ssnsetup(vcp);
if (error == 0)
vcp->vc_flags |= SMBV_AUTH_DONE;
done:
if (error) {
vcp->vc_flags &= ~(SMBV_GUEST_ACCESS | SMBV_KERBEROS_ACCESS | SMBV_ANONYMOUS_ACCESS);
SMB_STRFREE(vcp->vc_username);
SMB_STRFREE(vcp->vc_uppercase_username);
SMB_STRFREE(vcp->vc_pass);
SMB_STRFREE(vcp->vc_domain);
SMB_STRFREE(vcp->vc_gss.gss_cpn);
SMB_STRFREE(vcp->vc_gss.gss_spn);
}
smb_vc_put(vcp, context);
return error;
}
static int smb_vc_cmpshare(struct smb_vc *vcp, struct smb_share *ssp,
char *sh_name, vfs_context_t context)
{
if (strncmp(ssp->ss_name, sh_name, SMB_MAXUSERNAMELEN + 1) != 0)
return 1;
if (smb_vc_access(vcp, context) != 0)
return 1;
return (0);
}
static void smb_share_free(struct smb_connobj *cp)
{
struct smb_share *ssp = CPTOSS(cp);
SMB_STRFREE(ssp->ss_name);
SMB_STRFREE(ssp->ss_fsname);
lck_mtx_destroy(&ssp->ss_stlock, ssst_lck_group);
lck_mtx_destroy(&ssp->ss_mntlock, ssst_lck_group);
smb_co_done(SSTOCP(ssp));
free(ssp, M_SMBCONN);
}
static void smb_share_gone(struct smb_connobj *cp, vfs_context_t context)
{
struct smb_share *ssp = CPTOSS(cp);
DBG_ASSERT(ssp);
DBG_ASSERT(SSTOVC(ssp));
DBG_ASSERT(SSTOVC(ssp)->vc_iod);
smb_smb_treedisconnect(ssp, context);
}
void smb_share_ref(struct smb_share *ssp)
{
smb_co_ref(SSTOCP(ssp));
}
void smb_share_rele(struct smb_share *ssp, vfs_context_t context)
{
smb_co_rele(SSTOCP(ssp), context);
}
static int smb_vc_lookupshare(struct smb_vc *vcp, char *sh_name,
struct smb_share **sspp, vfs_context_t context)
{
struct smb_share *ssp = NULL;
int error;
SMBCO_FOREACH(ssp, VCTOCP(vcp)) {
if ((ssp->ss_tid != SMB_TID_UNKNOWN) &&
(smb_vc_cmpshare(vcp, ssp, sh_name, context) == 0)) {
smb_share_ref(ssp);
break;
}
}
if (ssp) {
*sspp = ssp;
error = 0;
} else {
*sspp = NULL;
error = ENOENT;
}
return error;
}
static int
smb_share_create(struct smb_vc *vcp, struct smbioc_share *shspec,
struct smb_share **sspp, vfs_context_t context)
{
struct smb_share *ssp;
ssp = smb_zmalloc(sizeof(*ssp), M_SMBCONN, M_WAITOK);
if (ssp == NULL)
return ENOMEM;
smb_co_init(SSTOCP(ssp), SMBL_SHARE, "smbss", vfs_context_proc(context));
ssp->obj.co_free = smb_share_free;
ssp->obj.co_gone = smb_share_gone;
lck_mtx_init(&ssp->ss_mntlock, ssst_lck_group, ssst_lck_attr);
lck_mtx_init(&ssp->ss_stlock, ssst_lck_group, ssst_lck_attr);
ssp->ss_name = smb_strdup(shspec->ioc_share, sizeof(shspec->ioc_share));
lck_mtx_lock(&ssp->ss_mntlock);
ssp->ss_mount = NULL;
lck_mtx_unlock(&ssp->ss_mntlock);
ssp->ss_type = shspec->ioc_stype;
ssp->ss_tid = SMB_TID_UNKNOWN;
ssp->ss_fsname = NULL;
smb_co_unlock(SSTOCP(ssp));
smb_co_addchild(VCTOCP(vcp), SSTOCP(ssp));
*sspp = ssp;
return (0);
}
int smb_sm_tcon(struct smb_vc *vcp, struct smbioc_share *shspec,
struct smb_share **shpp, vfs_context_t context)
{
int error;
*shpp = NULL;
error = smb_sm_lookupint(NULL, 0, NULL, 0, &vcp);
if (error) {
SMBERROR("The virtual circtuit was not found: error = %d\n", error);
return error;
}
error = smb_vc_lookupshare(vcp, shspec->ioc_share, shpp, context);
if (error == 0) {
smb_vc_unlock(vcp);
} else {
error = smb_share_create(vcp, shspec, shpp, context);
smb_vc_unlock(vcp);
if (error == 0) {
error = smb_smb_treeconnect(*shpp, context);
if (error) {
smb_share_rele(*shpp, context);
*shpp = NULL;
}
}
}
smb_vc_rele(vcp, context);
return error;
}
int smb_vc_access(struct smb_vc *vcp, vfs_context_t context)
{
if (vcp->vc_flags & SMBV_GUEST_ACCESS)
return(0);
if ((vfs_context_suser(context) == 0) ||
(kauth_cred_getuid(vfs_context_ucred(context)) == vcp->vc_uid))
return (0);
return (EACCES);
}
int smb_vc_negotiate(struct smb_vc *vcp, vfs_context_t context)
{
return smb_iod_request(vcp->vc_iod,
SMBIOD_EV_NEGOTIATE | SMBIOD_EV_SYNC, context);
}
int smb_vc_ssnsetup(struct smb_vc *vcp)
{
return smb_iod_request(vcp->vc_iod,
SMBIOD_EV_SSNSETUP | SMBIOD_EV_SYNC, NULL);
}
static char smb_emptypass[] = "";
const char * smb_vc_getpass(struct smb_vc *vcp)
{
if (vcp->vc_pass)
return vcp->vc_pass;
return smb_emptypass;
}
const char * smb_share_getpass(struct smb_share *ssp)
{
DBG_ASSERT(SSTOVC(ssp));
return smb_vc_getpass(SSTOVC(ssp));
}
u_short smb_vc_nextmid(struct smb_vc *vcp)
{
u_short r;
struct smb_connobj *cp = &vcp->obj;
lck_mtx_lock(&(cp)->co_interlock);
r = vcp->vc_mid++;
lck_mtx_unlock(&(cp)->co_interlock);
return r;
}
int smb_vc_reconnect_ref(struct smb_vc *vcp, vfs_context_t context)
{
int error;
error = smb_sm_lookupint(NULL, 0, NULL, 0, &vcp);
if (error)
return error;
smb_vc_unlock(vcp);
if (vcp->ss_flags & SMBO_GONE) {
smb_vc_rele(vcp, context);
error = ENOTCONN;
}
return error;
}
static void smb_reconnect_rel_thread(void *arg)
{
struct smbiod *iod = arg;
smb_vc_rele(iod->iod_vc, iod->iod_context);
}
void smb_vc_reconnect_rel(struct smb_vc *vcp)
{
struct smbiod *iod = vcp->vc_iod;
thread_t thread;
int error;
do {
error = kernel_thread_start((thread_continue_t)smb_reconnect_rel_thread,
iod, &thread);
if (error) {
struct timespec ts;
SMBERROR("Starting the reconnect vc release thread failed! %d\n",
error);
ts.tv_sec = 1;
ts.tv_nsec = 0;
msleep(iod, NULL, PWAIT | PCATCH, "smb_vc_reconnect_rel", &ts);
}
} while (error);
thread_deallocate(thread);
}