#include <sys/param.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/kpi_mbuf.h>
#include <sys/unistd.h>
#include <sys/mount.h>
#include <sys/vnode.h>
#include <sys/kauth.h>
#include <sys/smb_apple.h>
#include <netsmb/smb.h>
#include <netsmb/smb_conn.h>
#include <netsmb/smb_rq.h>
#include <netsmb/smb_tran.h>
#include <netsmb/smb_trantcp.h>
#include <smbfs/smbfs.h>
#include <netsmb/smb_compat4.h>
#include <IOKit/IOLib.h>
#include <netsmb/smb_sleephandler.h>
static int smb_iod_next;
static int smb_iod_sendall(struct smbiod *iod);
static int smb_iod_check_timeout(struct timespec *starttime, int SecondsTillTimeout)
{
struct timespec waittime, tsnow;
waittime.tv_sec = SecondsTillTimeout;
waittime.tv_nsec = 0;
timespecadd(&waittime, starttime);
nanouptime(&tsnow);
if (timespeccmp(&tsnow, &waittime, >))
return TRUE;
else return FALSE;
}
static __inline void
smb_iod_rqprocessed(struct smb_rq *rqp, int error, int flags)
{
SMBRQ_SLOCK(rqp);
rqp->sr_flags |= flags;
rqp->sr_lerror = error;
rqp->sr_rpgen++;
rqp->sr_state = SMBRQ_NOTIFIED;
if (rqp->sr_flags & SMBR_ASYNC) {
DBG_ASSERT(rqp->sr_callback);
rqp->sr_callback(rqp->sr_callback_args);
} else
wakeup(&rqp->sr_state);
SMBRQ_SUNLOCK(rqp);
}
static void smb_iod_softmount_tmeout(struct smbiod *iod)
{
struct smb_rq *rqp;
SMB_IOD_RQLOCK(iod);
TAILQ_FOREACH(rqp, &iod->iod_rqlist, sr_link) {
if ((rqp->sr_share) && (rqp->sr_share->ss_flags & SMBS_MNT_SOFT)) {
SMBDEBUG("Soft Mount timed out! cmd = %x\n", rqp->sr_cmd);
smb_iod_rqprocessed(rqp, ETIMEDOUT, 0);
}
}
SMB_IOD_RQUNLOCK(iod);
}
static void smb_iod_invrq(struct smbiod *iod)
{
struct smb_rq *rqp;
SMB_IOD_RQLOCK(iod);
TAILQ_FOREACH(rqp, &iod->iod_rqlist, sr_link) {
smb_iod_rqprocessed(rqp, ENOTCONN, SMBR_DEAD);
}
SMB_IOD_RQUNLOCK(iod);
}
static void
smb_iod_sockwakeup(struct smbiod *iod)
{
iod->iod_workflag = 1;
wakeup(&(iod)->iod_flags);
}
static void
smb_iod_closetran(struct smbiod *iod)
{
struct smb_vc *vcp = iod->iod_vc;
if (vcp->vc_tdata == NULL)
return;
SMB_TRAN_DISCONNECT(vcp);
SMB_TRAN_DONE(vcp);
}
static void
smb_iod_dead(struct smbiod *iod)
{
struct smb_rq *rqp;
iod->iod_state = SMBIOD_ST_DEAD;
smb_iod_closetran(iod);
smb_iod_invrq(iod);
SMB_IOD_RQLOCK(iod);
TAILQ_FOREACH(rqp, &iod->iod_rqlist, sr_link) {
if (rqp->sr_share)
smbfs_dead(rqp->sr_share);
}
SMB_IOD_RQUNLOCK(iod);
}
static void smb_iod_start_reconnect(struct smbiod *iod)
{
struct smb_vc *vcp;
struct smb_share *ssp;
struct smb_rq *rqp;
if (iod->iod_flags & SMBIOD_RECONNECT)
return;
switch (iod->iod_state) {
case SMBIOD_ST_VCACTIVE:
case SMBIOD_ST_RECONNECT:
break;
case SMBIOD_ST_NOTCONN:
case SMBIOD_ST_CONNECT:
case SMBIOD_ST_TRANACTIVE:
case SMBIOD_ST_NEGOACTIVE:
case SMBIOD_ST_SSNSETUP:
case SMBIOD_ST_DEAD:
SMBDEBUG("iod->iod_state = %x\n", iod->iod_state);
smb_iod_dead(iod);
return;
}
SMB_IOD_FLAGSLOCK(iod);
iod->iod_flags |= (SMBIOD_RECONNECT | SMBIOD_START_RECONNECT);
SMB_IOD_FLAGSUNLOCK(iod);
vcp = iod->iod_vc;
SMB_IOD_RQLOCK(iod);
TAILQ_FOREACH(rqp, &iod->iod_rqlist, sr_link) {
SMBRQ_SLOCK(rqp);
if (rqp->sr_flags & (SMBR_INTERNAL | SMBR_ASYNC)) {
SMBRQ_SUNLOCK(rqp);
if (rqp->sr_flags & SMBR_ASYNC)
smb_iod_rqprocessed(rqp, ETIMEDOUT, 0);
else
smb_iod_rqprocessed(rqp, ENOTCONN, SMBR_DEAD);
}
else {
rqp->sr_flags |= SMBR_RECONNECTED;
if (rqp->sr_state != SMBRQ_NOTIFIED) {
rqp->sr_state = SMBRQ_RECONNECT;
rqp->sr_flags |= SMBR_REXMIT;
rqp->sr_lerror = 0;
}
SMBRQ_SUNLOCK(rqp);
}
}
SMB_IOD_RQUNLOCK(iod);
smb_vc_lock(vcp);
SMBCO_FOREACH(ssp, VCTOCP(vcp)) {
lck_mtx_lock(&ssp->ss_stlock);
ssp->ss_flags |= SMBS_RECONNECTING;
lck_mtx_unlock(&(ssp)->ss_stlock);
}
smb_vc_unlock(vcp);
SMB_IOD_FLAGSLOCK(iod);
iod->iod_flags &= ~SMBIOD_START_RECONNECT;
SMB_IOD_FLAGSUNLOCK(iod);
}
static int
smb_iod_negotiate(struct smbiod *iod, vfs_context_t user_context)
{
struct smb_vc *vcp = iod->iod_vc;
int error;
SMBIODEBUG("%d\n", iod->iod_state);
switch(iod->iod_state) {
case SMBIOD_ST_TRANACTIVE:
case SMBIOD_ST_NEGOACTIVE:
case SMBIOD_ST_SSNSETUP:
SMBERROR("smb_iod_negotiate is invalid now, state=%d\n", iod->iod_state);
return EINVAL;
case SMBIOD_ST_VCACTIVE:
SMBERROR("smb_iod_negotiate called when connected\n");
return EISCONN;
case SMBIOD_ST_DEAD:
return ENOTCONN;
default:
break;
}
iod->iod_state = SMBIOD_ST_CONNECT;
error = 0;
itry {
ithrow(SMB_TRAN_CREATE(vcp));
SMBIODEBUG("tcreate\n");
if (vcp->vc_laddr) {
ithrow(SMB_TRAN_BIND(vcp, vcp->vc_laddr));
}
SMBIODEBUG("tbind\n");
SMB_TRAN_SETPARAM(vcp, SMBTP_SELECTID, iod);
SMB_TRAN_SETPARAM(vcp, SMBTP_UPCALL, smb_iod_sockwakeup);
ithrow(SMB_TRAN_CONNECT(vcp, vcp->vc_saddr));
iod->iod_state = SMBIOD_ST_TRANACTIVE;
SMBIODEBUG("tconnect\n");
ithrow(smb_smb_negotiate(vcp, iod->iod_context, user_context, FALSE));
iod->iod_state = SMBIOD_ST_NEGOACTIVE;
SMBIODEBUG("completed\n");
smb_iod_invrq(iod);
} icatch(error) {
smb_iod_dead(iod);
} ifinally {
} iendtry;
return error;
}
static int
smb_iod_ssnsetup(struct smbiod *iod)
{
struct smb_vc *vcp = iod->iod_vc;
int error;
SMBIODEBUG("%d\n", iod->iod_state);
switch(iod->iod_state) {
case SMBIOD_ST_NEGOACTIVE:
break;
case SMBIOD_ST_DEAD:
return ENOTCONN;
case SMBIOD_ST_VCACTIVE:
SMBERROR("smb_iod_ssnsetup called when connected\n");
return EISCONN;
default:
SMBERROR("smb_iod_ssnsetup is invalid now, state=%d\n",
iod->iod_state);
return EINVAL;
}
error = 0;
iod->iod_state = SMBIOD_ST_SSNSETUP;
itry {
ithrow(smb_smb_ssnsetup(vcp, iod->iod_context));
iod->iod_state = SMBIOD_ST_VCACTIVE;
SMBIODEBUG("completed\n");
if ((iod->iod_flags & SMBIOD_RECONNECT) != SMBIOD_RECONNECT)
smb_iod_invrq(iod);
} icatch(error) {
if (iod->iod_state == SMBIOD_ST_SSNSETUP)
iod->iod_state = SMBIOD_ST_NEGOACTIVE;
} ifinally {
} iendtry;
return error;
}
static int
smb_iod_disconnect(struct smbiod *iod)
{
struct smb_vc *vcp = iod->iod_vc;
SMBIODEBUG("\n");
if (iod->iod_state == SMBIOD_ST_VCACTIVE) {
smb_smb_ssnclose(vcp, iod->iod_context);
iod->iod_state = SMBIOD_ST_TRANACTIVE;
}
vcp->vc_smbuid = SMB_UID_UNKNOWN;
smb_iod_closetran(iod);
iod->iod_state = SMBIOD_ST_NOTCONN;
return 0;
}
static int
smb_iod_sendrq(struct smbiod *iod, struct smb_rq *rqp)
{
struct smb_vc *vcp = iod->iod_vc;
struct smb_share *ssp = rqp->sr_share;
mbuf_t m;
int error;
SMBIODEBUG("iod_state = %d\n", iod->iod_state);
switch (iod->iod_state) {
case SMBIOD_ST_NOTCONN:
smb_iod_rqprocessed(rqp, ENOTCONN, 0);
return 0;
case SMBIOD_ST_DEAD:
smb_iod_rqprocessed(rqp, ENOTCONN, 0);
return 0;
case SMBIOD_ST_CONNECT:
return 0;
case SMBIOD_ST_NEGOACTIVE:
SMBERROR("smb_iod_sendrq in unexpected state(%d)\n",
iod->iod_state);
default:
break;
}
*rqp->sr_rquid = htoles(vcp ? vcp->vc_smbuid : 0);
if (vcp && !vcp->vc_smbuid && vcp->vc_hflags2 & SMB_FLAGS2_EXT_SEC)
*rqp->sr_rqtid = htoles(0);
else
*rqp->sr_rqtid = htoles(ssp ? ssp->ss_tid : SMB_TID_UNKNOWN);
mb_fixhdr(&rqp->sr_rq);
if (vcp->vc_hflags2 & SMB_FLAGS2_SECURITY_SIGNATURE)
smb_rq_sign(rqp);
SMBSDEBUG("M:%04x, P:%04x, U:%04x, T:%04x\n", rqp->sr_mid, 0, 0, 0);
m_dumpm(rqp->sr_rq.mb_top);
error = mbuf_copym(rqp->sr_rq.mb_top, 0, MBUF_COPYALL, MBUF_WAITOK, &m);
DBG_ASSERT(error == 0);
error = rqp->sr_lerror = (error) ? error : SMB_TRAN_SEND(vcp, m);
if (error == 0) {
nanouptime(&rqp->sr_timesent);
iod->iod_lastrqsent = rqp->sr_timesent;
rqp->sr_state = SMBRQ_SENT;
return 0;
}
if (SMB_TRAN_FATAL(vcp, error))
return ENOTCONN;
else if (error) {
SMBERROR("TRAN_SEND returned non-fatal error %d\n", error);
smb_iod_rqprocessed(rqp, error, 0);
}
return 0;
}
static int
smb_iod_recvall(struct smbiod *iod)
{
struct smb_vc *vcp = iod->iod_vc;
struct smb_rq *rqp;
mbuf_t m;
u_char *hp;
u_short mid;
u_int8_t cmd;
int error;
switch (iod->iod_state) {
case SMBIOD_ST_NOTCONN:
case SMBIOD_ST_DEAD:
case SMBIOD_ST_CONNECT:
return 0;
default:
break;
}
for (;;) {
m = NULL;
error = SMB_TRAN_RECV(vcp, &m);
if (error == EWOULDBLOCK)
break;
if (SMB_TRAN_FATAL(vcp, error)) {
smb_iod_start_reconnect(iod);
break;
}
if (error)
break;
if (m == NULL) {
SMBERROR("tran return NULL without error\n");
error = EPIPE;
continue;
}
if (mbuf_pullup(&m, SMB_HDRLEN))
continue;
m_dumpm(m);
hp = mbuf_data(m);
if (bcmp(hp, SMB_SIGNATURE, SMB_SIGLEN) != 0) {
mbuf_freem(m);
continue;
}
mid = SMB_HDRMID(hp);
cmd = SMB_HDRCMD(hp);
SMBSDEBUG("mid %04x cmd = 0x%x\n", (u_int)mid, cmd);
SMB_IOD_RQLOCK(iod);
nanouptime(&iod->iod_lastrecv);
TAILQ_FOREACH(rqp, &iod->iod_rqlist, sr_link) {
if ((rqp->sr_mid != mid) || (rqp->sr_cmd != cmd))
continue;
if (rqp->sr_share) {
SMB_IOD_FLAGSLOCK(iod);
iod->iod_flags &= ~SMBIOD_VC_NOTRESP;
SMB_IOD_FLAGSUNLOCK(iod);
smbfs_up(rqp->sr_share);
}
SMBRQ_SLOCK(rqp);
if (rqp->sr_rp.md_top == NULL) {
md_initm(&rqp->sr_rp, m);
} else {
if (rqp->sr_flags & SMBR_MULTIPACKET) {
md_append_record(&rqp->sr_rp, m);
} else {
SMBRQ_SUNLOCK(rqp);
SMBERROR("duplicate response %d (ignored)\n", mid);
break;
}
}
SMBRQ_SUNLOCK(rqp);
smb_iod_rqprocessed(rqp, 0, 0);
break;
}
SMB_IOD_RQUNLOCK(iod);
if (rqp == NULL) {
if ((cmd != SMB_COM_ECHO) && (cmd != SMB_COM_NT_TRANSACT))
SMBWARNING("drop resp: mid %d, cmd %d\n", (u_int)mid, cmd);
mbuf_freem(m);
}
}
if ((iod->iod_flags & SMBIOD_RECONNECT) != SMBIOD_RECONNECT) {
SMB_IOD_RQLOCK(iod);
TAILQ_FOREACH(rqp, &iod->iod_rqlist, sr_link) {
if (smb_sigintr(rqp->sr_context))
rqp->sr_timo = SMBIOD_INTR_TIMO;
}
SMB_IOD_RQUNLOCK(iod);
}
return 0;
}
int
smb_iod_request(struct smbiod *iod, int event, void *ident)
{
struct smbiod_event *evp;
int error;
SMBIODEBUG("\n");
evp = smb_zmalloc(sizeof(*evp), M_SMBIOD, M_WAITOK);
evp->ev_type = event;
evp->ev_ident = ident;
SMB_IOD_EVLOCK(iod);
STAILQ_INSERT_TAIL(&iod->iod_evlist, evp, ev_link);
if ((event & SMBIOD_EV_SYNC) == 0) {
SMB_IOD_EVUNLOCK(iod);
smb_iod_wakeup(iod);
return 0;
}
smb_iod_wakeup(iod);
msleep(evp, SMB_IOD_EVLOCKPTR(iod), PWAIT | PDROP, "iod-ev", 0);
error = evp->ev_error;
free(evp, M_SMBIOD);
return error;
}
int
smb_iod_rq_enqueue(struct smb_rq *rqp)
{
struct smb_vc *vcp = rqp->sr_vc;
struct smbiod *iod = vcp->vc_iod;
struct timespec ts;
if (rqp->sr_context == iod->iod_context) {
DBG_ASSERT((rqp->sr_flags & SMBR_ASYNC) != SMBR_ASYNC);
rqp->sr_flags |= SMBR_INTERNAL;
SMB_IOD_RQLOCK(iod);
TAILQ_INSERT_HEAD(&iod->iod_rqlist, rqp, sr_link);
SMB_IOD_RQUNLOCK(iod);
for (;;) {
if (smb_iod_sendrq(iod, rqp) != 0) {
smb_iod_start_reconnect(iod);
break;
}
if (rqp->sr_state != SMBRQ_NOTSENT)
break;
ts.tv_sec = 1;
ts.tv_nsec = 0;
msleep(&iod->iod_flags, 0, PWAIT, "90sndw", &ts);
}
if (rqp->sr_lerror)
smb_iod_removerq(rqp);
return rqp->sr_lerror;
}
switch (iod->iod_state) {
case SMBIOD_ST_DEAD:
if (rqp->sr_share)
smbfs_dead(rqp->sr_share);
case SMBIOD_ST_NOTCONN:
return ENOTCONN;
case SMBIOD_ST_TRANACTIVE:
case SMBIOD_ST_NEGOACTIVE:
case SMBIOD_ST_SSNSETUP:
default:
if ((!(rqp->sr_flags & SMBR_INTERNAL)) && (iod->iod_flags & SMBIOD_RECONNECT) &&
(rqp->sr_share->ss_flags & SMBS_MNT_SOFT)) {
if (smb_iod_check_timeout(&iod->reconnectStartTime, SOFTMOUNT_TIMEOUT)) {
SMBDEBUG("Soft Mount timed out! cmd = %x\n", rqp->sr_cmd);
return ETIMEDOUT;
}
}
break;
}
SMB_IOD_RQLOCK(iod);
for (;;) {
DBG_ASSERT((vcp->vc_maxmux));
if (vcp->vc_maxmux == 0) {
SMBERROR("maxmux == 0\n");
SMB_IOD_RQUNLOCK(iod);
smb_iod_dead(iod);
return EPIPE;
}
if (iod->iod_muxcnt < vcp->vc_maxmux)
break;
iod->iod_muxwant++;
msleep(&iod->iod_muxwant, SMB_IOD_RQLOCKPTR(iod), PWAIT, "iod-rq-mux", 0);
}
if (rqp->sr_flags & SMBR_ASYNC) {
if (iod->iod_asynccnt >= ((vcp->vc_maxmux / 3) * 2)) {
SMBWARNING("Max out on VC async notify request %d\n", iod->iod_asynccnt);
SMB_IOD_RQUNLOCK(iod);
return EWOULDBLOCK;
}
iod->iod_asynccnt++;
} else if (iod->iod_state == SMBIOD_ST_VCACTIVE) {
if (vcp->throttle_info)
throttle_info_update(vcp->throttle_info, 0);
}
iod->iod_muxcnt++;
TAILQ_INSERT_TAIL(&iod->iod_rqlist, rqp, sr_link);
SMB_IOD_RQUNLOCK(iod);
iod->iod_workflag = 1;
smb_iod_wakeup(iod);
return 0;
}
int
smb_iod_removerq(struct smb_rq *rqp)
{
struct smb_vc *vcp = rqp->sr_vc;
struct smbiod *iod = vcp->vc_iod;
SMBIODEBUG("\n");
SMB_IOD_RQLOCK(iod);
if (rqp->sr_flags & SMBR_INTERNAL) {
TAILQ_REMOVE(&iod->iod_rqlist, rqp, sr_link);
SMB_IOD_RQUNLOCK(iod);
return 0;
}
while (rqp->sr_flags & SMBR_XLOCK) {
rqp->sr_flags |= SMBR_XLOCKWANT;
msleep(rqp, SMB_IOD_RQLOCKPTR(iod), PWAIT, "iod-rq-rm", 0);
}
TAILQ_REMOVE(&iod->iod_rqlist, rqp, sr_link);
if (rqp->sr_flags & SMBR_ASYNC)
iod->iod_asynccnt--;
iod->iod_muxcnt--;
if (iod->iod_muxwant) {
iod->iod_muxwant--;
wakeup(&iod->iod_muxwant);
}
SMB_IOD_RQUNLOCK(iod);
return 0;
}
int
smb_iod_waitrq(struct smb_rq *rqp)
{
struct smbiod *iod = rqp->sr_vc->vc_iod;
int error;
struct timespec ts;
SMBIODEBUG("\n");
if (rqp->sr_flags & SMBR_INTERNAL) {
for (;;) {
smb_iod_sendall(iod);
smb_iod_recvall(iod);
if (rqp->sr_rpgen != rqp->sr_rplast)
break;
ts.tv_sec = 1;
ts.tv_nsec = 0;
msleep(&iod->iod_flags, 0, PWAIT, "90irq", &ts);
}
smb_iod_removerq(rqp);
return rqp->sr_lerror;
}
SMBRQ_SLOCK(rqp);
if (rqp->sr_rpgen == rqp->sr_rplast) {
do {
ts.tv_sec = 15;
ts.tv_nsec = 0;
msleep(&rqp->sr_state, SMBRQ_SLOCKPTR(rqp), PWAIT, "srs-rq", &ts);
if ((rqp->sr_rplast) && (rqp->sr_rpgen == rqp->sr_rplast) &&
((rqp->sr_flags & (SMBR_MULTIPACKET | SMBR_RECONNECTED | SMBR_REXMIT)) == (SMBR_MULTIPACKET | SMBR_RECONNECTED))) {
SMBERROR("Reconnect in the middle of a transaction messages, just return ETIMEDOUT\n");
rqp->sr_lerror = ETIMEDOUT;
}
} while ((rqp->sr_lerror == 0) && (rqp->sr_rpgen == rqp->sr_rplast));
}
rqp->sr_rplast++;
SMBRQ_SUNLOCK(rqp);
error = rqp->sr_lerror;
if (rqp->sr_flags & SMBR_MULTIPACKET) {
SMB_IOD_RQLOCK(iod);
TAILQ_REMOVE(&iod->iod_rqlist, rqp, sr_link);
TAILQ_INSERT_TAIL(&iod->iod_rqlist, rqp, sr_link);
SMB_IOD_RQUNLOCK(iod);
} else
smb_iod_removerq(rqp);
return error;
}
void
smb_iod_shutdown_share(struct smb_share *ssp)
{
struct smbiod *iod = SSTOVC(ssp)->vc_iod;
struct smb_rq *rqp;
SMB_IOD_RQLOCK(iod);
TAILQ_FOREACH(rqp, &iod->iod_rqlist, sr_link) {
if (rqp->sr_state != SMBRQ_NOTIFIED && rqp->sr_share == ssp)
smb_iod_rqprocessed(rqp, ENXIO, 0);
}
SMB_IOD_RQUNLOCK(iod);
}
static int
smb_iod_sendall(struct smbiod *iod)
{
struct smb_vc *vcp = iod->iod_vc;
struct smb_rq *rqp;
struct timespec now, ts, uetimeout;
int herror, echo;
herror = 0;
echo = 0;
SMB_IOD_RQLOCK(iod);
TAILQ_FOREACH(rqp, &iod->iod_rqlist, sr_link) {
if (iod->iod_state == SMBIOD_ST_DEAD) {
smb_iod_rqprocessed(rqp, ETIMEDOUT, 0);
continue;
}
if ((iod->iod_flags & SMBIOD_VC_NOTRESP) && (rqp->sr_share)) {
struct smb_share* ssp = rqp->sr_share;
int isforced;
lck_mtx_lock(&ssp->ss_mntlock);
isforced = (ssp->ss_mount && (vfs_isforce(ssp->ss_mount->sm_mp)));
lck_mtx_unlock(&ssp->ss_mntlock);
if (isforced) {
smb_iod_rqprocessed(rqp, ETIMEDOUT, 0);
continue;
}
}
if ((iod->iod_flags & SMBIOD_RECONNECT) && (!(rqp->sr_flags & SMBR_INTERNAL))) {
if (rqp->sr_flags & SMBR_ASYNC) {
smb_iod_rqprocessed(rqp, ETIMEDOUT, 0);
continue;
}
DBG_ASSERT(rqp->sr_state != SMBRQ_SENT)
if (rqp->sr_state == SMBRQ_NOTSENT)
rqp->sr_state = SMBRQ_RECONNECT;
rqp->sr_flags |= SMBR_RECONNECTED;
}
switch (rqp->sr_state) {
case SMBRQ_RECONNECT:
if (iod->iod_flags & SMBIOD_RECONNECT)
break;
rqp->sr_state = SMBRQ_NOTSENT;
rqp->sr_reconnect_cnt += 1;
if (rqp->sr_reconnect_cnt > MAX_SR_RECONNECT_CNT) {
SMBERROR("Looks like we are in a reconnect loop with server %s, canceling the reconnect. (cmd = %x)\n",
vcp->vc_srvname, rqp->sr_cmd);
iod->iod_state = SMBIOD_ST_DEAD;
smb_iod_rqprocessed(rqp, ETIMEDOUT, 0);
continue;
}
case SMBRQ_NOTSENT:
rqp->sr_flags |= SMBR_XLOCK;
SMB_IOD_RQUNLOCK(iod);
herror = smb_iod_sendrq(iod, rqp);
SMB_IOD_RQLOCK(iod);
rqp->sr_flags &= ~SMBR_XLOCK;
if (rqp->sr_flags & SMBR_XLOCKWANT) {
rqp->sr_flags &= ~SMBR_XLOCKWANT;
wakeup(rqp);
}
break;
case SMBRQ_SENT:
if (rqp->sr_flags & SMBR_ASYNC)
break;
nanouptime(&now);
if (rqp->sr_share) {
ts = now;
uetimeout.tv_sec = SMB_RESP_WAIT_TIMO;
uetimeout.tv_nsec = 0;
timespecsub(&ts, &uetimeout);
if (timespeccmp(&ts, &iod->iod_lastrecv, >) && timespeccmp(&ts, &rqp->sr_timesent, >)) {
herror = ENOTCONN;
break;
}
}
ts.tv_sec = SMB_SEND_WAIT_TIMO;
ts.tv_nsec = 0;
timespecadd(&ts, &rqp->sr_timesent);
if (timespeccmp(&now, &ts, >)) {
SMBERROR("Timed out waiting on the response for 0x%x mid = 0x%x\n", rqp->sr_cmd, rqp->sr_mid);
smb_iod_rqprocessed(rqp, ETIMEDOUT, 0);
} else if (rqp->sr_cmd != SMB_COM_ECHO) {
ts = now;
uetimeout.tv_sec = SMBUETIMEOUT;
uetimeout.tv_nsec = 0;
timespecsub(&ts, &uetimeout);
if (timespeccmp(&ts, &rqp->sr_timesent, >))
echo++;
}
break;
default:
break;
}
if (herror)
break;
}
SMB_IOD_RQUNLOCK(iod);
if (herror == ENOTCONN) {
smb_iod_start_reconnect(iod);
}
else if (echo && ((iod->iod_flags & SMBIOD_RECONNECT) != SMBIOD_RECONNECT)) {
nanouptime(&ts);
uetimeout.tv_sec = SMBUETIMEOUT;
uetimeout.tv_nsec = 0;
timespecsub(&ts, &uetimeout);
if (timespeccmp(&ts, &iod->iod_lastrecv, >) &&
timespeccmp(&ts, &iod->iod_lastrqsent, >))
(void)smb_smb_echo(vcp, iod->iod_context, SMBNOREPLYWAIT);
}
return 0;
}
static void
smb_tickle(struct smbiod *iod)
{
struct smb_vc *vcp = iod->iod_vc;
struct smb_share *ssp = NULL;
int error;
smb_vc_lock(iod->iod_vc);
SMBCO_FOREACH(ssp, VCTOCP(vcp)) {
smb_share_ref(ssp);
if (ssp->ss_flags & SMBO_GONE)
error = ENOTCONN;
else
error = smb_smb_checkdir(ssp, NULL, "", 0, iod->iod_context);
smb_share_rele(ssp, iod->iod_context);
if (!error)
break;
}
smb_vc_unlock(iod->iod_vc);
}
static int smb_iod_check_for_active_shares(struct smb_vc *vcp, int NotifyUser)
{
struct smbiod * iod = vcp->vc_iod;
struct smb_share *ssp;
int treecnt = 0;
int share_has_mount;
smb_vc_lock(vcp);
SMBCO_FOREACH(ssp, VCTOCP(vcp)) {
smb_share_ref(ssp);
if (ssp->ss_flags & SMBO_GONE) {
smb_share_rele(ssp, iod->iod_context);
}
else if (ssp->ss_flags & SMBS_INMOUNT) {
treecnt++;
smb_share_rele(ssp, iod->iod_context);
} else {
lck_mtx_lock(&ssp->ss_mntlock);
if (ssp->ss_mount && (!vfs_isforce(ssp->ss_mount->sm_mp))) {
if ((ssp->ss_flags & SMBS_MNT_SOFT) && (vfs_isunmount(ssp->ss_mount->sm_mp))) {
}
else {
treecnt++;
}
share_has_mount = TRUE;
}
else
share_has_mount = FALSE;
lck_mtx_unlock(&ssp->ss_mntlock);
if (NotifyUser && share_has_mount)
smbfs_down(ssp);
smb_share_rele(ssp, iod->iod_context);
}
}
smb_vc_unlock(vcp);
return treecnt;
}
int smb_iod_nb_intr(struct smb_vc *vcp)
{
struct smbiod * iod = vcp->vc_iod;
int NotifyUser = FALSE;
if ((iod->iod_flags & SMBIOD_RECONNECT) != SMBIOD_RECONNECT) {
if (vcp->connect_flag && (*(vcp->connect_flag) & NSMBFL_CANCEL))
return EINTR;
else return 0;
}
if (((iod->iod_flags & SMBIOD_VC_NOTRESP) == 0) &&
(smb_iod_check_timeout(&iod->reconnectStartTime, NOTIFY_USER_TIMEOUT))) {
NotifyUser = TRUE;
SMB_IOD_FLAGSLOCK(iod);
iod->iod_flags |= SMBIOD_VC_NOTRESP;
SMB_IOD_FLAGSUNLOCK(iod);
}
if (smb_iod_check_timeout(&iod->reconnectStartTime, SOFTMOUNT_TIMEOUT)) {
smb_iod_softmount_tmeout(iod);
}
if (smb_iod_check_for_active_shares(vcp, NotifyUser))
return 0;
else return EINTR;
}
static void smb_iod_reconnect(struct smbiod *iod)
{
struct smb_vc *vcp = iod->iod_vc;
int tree_cnt = 0;
int error = 0;
int sleepcnt = 0;
struct smb_share *ssp = NULL;
struct timespec waittime, sleeptime, tsnow;
int ii;
if (smb_vc_reconnect_ref(iod->iod_vc, iod->iod_context)) {
iod->iod_flags &= ~(SMBIOD_RECONNECT | SMBIOD_START_RECONNECT);
iod->iod_workflag = 1;
SMBERROR("The vc is going aways while we are in reconnect?\n");
return;
}
SMBDEBUG("STARTING RECONNECT WITH %s\n", vcp->vc_srvname);
SMB_TRAN_DISCONNECT(vcp);
iod->iod_state = SMBIOD_ST_CONNECT;
sleepcnt = 1;
sleeptime.tv_sec = 1;
sleeptime.tv_nsec = 0;
nanouptime(&iod->reconnectStartTime);
waittime.tv_sec = vcp->reconnect_wait_time;
waittime.tv_nsec = 0;
timespecadd(&waittime, &iod->reconnectStartTime);
do {
error = smb_iod_nb_intr(vcp);
if (! error)
error = SMB_TRAN_CONNECT(vcp, vcp->vc_saddr);
if (error == EINTR) {
SMBDEBUG("The reconnection to %s, was canceled\n", vcp->vc_srvname);
goto exit;
}
DBG_ASSERT(vcp->vc_tdata != NULL);
DBG_ASSERT(error != EISCONN);
DBG_ASSERT(error != EINVAL);
if (error) {
for (ii= 1; ii <= sleepcnt; ii++) {
msleep(&iod->iod_flags, 0, PWAIT, "smb_iod_reconnect", &sleeptime);
if (smb_iod_nb_intr(vcp) == EINTR) {
error = EINTR;
SMBDEBUG("The reconnection to %s, was canceled\n", vcp->vc_srvname);
goto exit;
}
}
if (sleepcnt < SMB_MAX_SLEEP_CNT )
sleepcnt++;
SMBERROR("Retrying connection to %s error = %d\n", vcp->vc_srvname, error);
}
if (iod->reconnectStartTime.tv_sec < gWakeTime.tv_sec) {
sleepcnt = 1;
nanouptime(&iod->reconnectStartTime);
waittime.tv_sec = vcp->reconnect_wait_time;
waittime.tv_nsec = 0;
timespecadd(&waittime, &iod->reconnectStartTime);
}
if (!error) {
iod->iod_muxcnt = 0;
smb_vc_reset(vcp);
iod->iod_state = SMBIOD_ST_TRANACTIVE;
error = smb_smb_negotiate(vcp, iod->iod_context, NULL, TRUE);
if ((error == ENOTCONN) || (error == ETIMEDOUT)) {
SMBWARNING("The negotiate timed out to %s trying again: error = %d\n", vcp->vc_srvname, error);
SMB_TRAN_DISCONNECT(vcp);
iod->iod_state = SMBIOD_ST_CONNECT;
} else if (error) {
SMBWARNING("The negotiate failed to %s with an error of %d\n", vcp->vc_srvname, error);
break;
} else {
SMBDEBUG("The negotiate succeed to %s\n", vcp->vc_srvname);
iod->iod_state = SMBIOD_ST_NEGOACTIVE;
error = smb_iod_ssnsetup(iod);
if (error)
SMBWARNING("The authentication failed to %s with an error of %d\n", vcp->vc_srvname, error);
if (error != EAGAIN)
break;
for (ii=1; ii < SMB_MAX_SLEEP_CNT; ii++) {
msleep(&iod->iod_flags, 0, PWAIT, "smb_iod_reconnect", &sleeptime);
error = smb_iod_ssnsetup(iod);
if (error)
SMBWARNING("Retring authentication count %d failed to %s with an error of %d\n", ii, vcp->vc_srvname, error);
if (error != EAGAIN)
break;
}
if (error == 0)
break;
SMB_TRAN_DISCONNECT(vcp);
iod->iod_state = SMBIOD_ST_CONNECT;
error = EAUTH;
}
}
nanouptime(&tsnow);
} while (error && (timespeccmp(&waittime, &tsnow, >)));
if (error) {
SMBWARNING("The connection failed to %s with an error of %d\n", vcp->vc_srvname, error);
goto exit;
}
tree_cnt = 0;
smb_vc_lock(vcp);
SMBCO_FOREACH(ssp, VCTOCP(vcp)) {
int share_has_mount;
smb_share_ref(ssp);
if (ssp->ss_flags & SMBO_GONE) {
smb_share_rele(ssp, iod->iod_context);
continue;
}
lck_mtx_lock(&ssp->ss_mntlock);
if (ssp->ss_mount && (!vfs_isforce(ssp->ss_mount->sm_mp)))
share_has_mount = TRUE;
else
share_has_mount = FALSE;
lck_mtx_unlock(&ssp->ss_mntlock);
if (share_has_mount) {
int tree_error = smb_smb_treeconnect(ssp, iod->iod_context);
if (tree_error == 0) {
smbfs_reconnect(ssp);
smbfs_up(ssp);
tree_cnt++;
SMBERROR("Reconnected share %s with server %s\n", ssp->ss_name, vcp->vc_srvname);
} else
SMBERROR("Reconnection failed to share %s on server %s error = %d\n", ssp->ss_name, vcp->vc_srvname, tree_error);
}
smb_share_rele(ssp, iod->iod_context);
}
smb_vc_unlock(vcp);
if (!tree_cnt) {
SMBWARNING("No mounted volumes in reconnect, closing connection to server %s\n",vcp->vc_srvname);
error = ENOTCONN;
}
exit:
if ((error == 0) || (iod->reconnectStartTime.tv_sec >= gWakeTime.tv_sec)) {
smb_vc_lock(vcp);
SMBCO_FOREACH(ssp, VCTOCP(vcp)) {
smb_share_ref(ssp);
lck_mtx_lock(&ssp->ss_stlock);
ssp->ss_flags &= ~SMBS_RECONNECTING;
lck_mtx_unlock(&ssp->ss_stlock);
wakeup(&ssp->ss_flags);
smb_share_rele(ssp, iod->iod_context);
}
smb_vc_unlock(vcp);
}
SMB_IOD_FLAGSLOCK(iod);
iod->iod_flags &= ~SMBIOD_RECONNECT;
SMB_IOD_FLAGSUNLOCK(iod);
if (error)
SMB_TRAN_DISCONNECT(vcp);
smb_vc_reconnect_rel(vcp);
if (error) {
if (iod->reconnectStartTime.tv_sec < gWakeTime.tv_sec) {
SMBWARNING("The reconnect failed because we went to sleep retrying! %d\n", error);
iod->iod_state = SMBIOD_ST_RECONNECT;
smb_iod_start_reconnect(iod);
} else {
smb_iod_dead(iod);
}
}
iod->iod_workflag = 1;
}
static __inline void
smb_iod_main(struct smbiod *iod)
{
struct smbiod_event *evp;
struct timespec tsnow;
int error;
SMBIODEBUG("\n");
error = 0;
for (;;) {
SMB_IOD_EVLOCK(iod);
evp = STAILQ_FIRST(&iod->iod_evlist);
if (evp == NULL) {
SMB_IOD_EVUNLOCK(iod);
break;
}
else if (iod->iod_flags & SMBIOD_RECONNECT) {
SMB_IOD_EVUNLOCK(iod);
break;
}
STAILQ_REMOVE_HEAD(&iod->iod_evlist, ev_link);
evp->ev_type |= SMBIOD_EV_PROCESSING;
SMB_IOD_EVUNLOCK(iod);
switch (evp->ev_type & SMBIOD_EV_MASK) {
case SMBIOD_EV_NEGOTIATE:
evp->ev_error = smb_iod_negotiate(iod, evp->ev_ident);
break;
case SMBIOD_EV_SSNSETUP:
evp->ev_error = smb_iod_ssnsetup(iod);
break;
case SMBIOD_EV_DISCONNECT:
evp->ev_error = smb_iod_disconnect(iod);
break;
case SMBIOD_EV_SHUTDOWN:
iod->iod_flags |= SMBIOD_SHUTDOWN;
break;
case SMBIOD_EV_NEWRQ:
break;
default:
break;
}
if (evp->ev_type & SMBIOD_EV_SYNC) {
SMB_IOD_EVLOCK(iod);
wakeup(evp);
SMB_IOD_EVUNLOCK(iod);
} else
free(evp, M_SMBIOD);
}
if ((iod->iod_state == SMBIOD_ST_VCACTIVE) && ((iod->iod_flags & SMBIOD_RECONNECT) != SMBIOD_RECONNECT)) {
nanouptime(&tsnow);
timespecsub(&tsnow, &iod->iod_pingtimo);
if (timespeccmp(&tsnow, &iod->iod_lastrqsent, >))
smb_tickle(iod);
}
smb_iod_sendall(iod);
smb_iod_recvall(iod);
return;
}
static void smb_iod_thread(void *arg)
{
struct smbiod *iod = arg;
vfs_context_t context;
context = iod->iod_context = vfs_context_create((vfs_context_t)0);
SMB_IOD_FLAGSLOCK(iod);
iod->iod_flags |= SMBIOD_RUNNING;
SMB_IOD_FLAGSUNLOCK(iod);
while ((iod->iod_flags & SMBIOD_SHUTDOWN) == 0) {
iod->iod_workflag = 0;
smb_iod_main(iod);
if (iod->iod_flags & SMBIOD_SHUTDOWN)
break;
if ((iod->iod_flags & (SMBIOD_START_RECONNECT | SMBIOD_RECONNECT)) == SMBIOD_RECONNECT)
smb_iod_reconnect(iod);
else if ((iod->iod_flags & SMBIOD_VC_NOTRESP) && ((iod->iod_state & SMBIOD_ST_DEAD) != SMBIOD_ST_DEAD))
{
if (smb_iod_check_for_active_shares(iod->iod_vc, FALSE) == 0) {
smb_iod_dead(iod);
continue;
}
}
if (iod->iod_workflag)
continue;
SMBIODEBUG("going to sleep for %d secs %d nsecs\n", iod->iod_sleeptimespec.tv_sec,
iod->iod_sleeptimespec.tv_nsec);
msleep(&iod->iod_flags, 0, PWAIT, "iod thread idle", &iod->iod_sleeptimespec);
}
SMB_IOD_FLAGSLOCK(iod);
iod->iod_flags &= ~SMBIOD_RUNNING;
wakeup(iod);
SMB_IOD_FLAGSUNLOCK(iod);
vfs_context_rele(context);
}
int
smb_iod_create(struct smb_vc *vcp)
{
struct smbiod *iod;
kern_return_t result;
thread_t thread;
iod = smb_zmalloc(sizeof(*iod), M_SMBIOD, M_WAITOK);
iod->iod_id = smb_iod_next++;
iod->iod_state = SMBIOD_ST_NOTCONN;
lck_mtx_init(&iod->iod_flagslock, iodflags_lck_group, iodflags_lck_attr);
iod->iod_vc = vcp;
iod->iod_sleeptimespec.tv_sec = SMBIOD_SLEEP_TIMO;
iod->iod_sleeptimespec.tv_nsec = 0;
iod->iod_pingtimo.tv_sec = SMBIOD_PING_TIMO;
nanouptime(&iod->iod_lastrqsent);
vcp->vc_iod = iod;
lck_mtx_init(&iod->iod_rqlock, iodrq_lck_group, iodrq_lck_attr);
TAILQ_INIT(&iod->iod_rqlist);
lck_mtx_init(&iod->iod_evlock, iodev_lck_group, iodev_lck_attr);
STAILQ_INIT(&iod->iod_evlist);
result = kernel_thread_start((thread_continue_t)smb_iod_thread, iod, &thread);
if (result != KERN_SUCCESS) {
SMBERROR("can't start smbiod result = %d\n", result);
free(iod, M_SMBIOD);
return (ENOMEM);
}
thread_deallocate(thread);
return (0);
}
int
smb_iod_destroy(struct smbiod *iod)
{
smb_iod_request(iod, SMBIOD_EV_SHUTDOWN, NULL);
for (;;) {
SMB_IOD_FLAGSLOCK(iod);
if (!(iod->iod_flags & SMBIOD_RUNNING)) {
SMB_IOD_FLAGSUNLOCK(iod);
break;
}
msleep(iod, SMB_IOD_FLAGSLOCKPTR(iod), PWAIT | PDROP,
"iod-exit", 0);
}
lck_mtx_destroy(&iod->iod_flagslock, iodflags_lck_group);
lck_mtx_destroy(&iod->iod_rqlock, iodrq_lck_group);
lck_mtx_destroy(&iod->iod_evlock, iodev_lck_group);
free(iod, M_SMBIOD);
return 0;
}
int
smb_iod_init(void)
{
return 0;
}
int
smb_iod_done(void)
{
return 0;
}