| /* |
| * 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_iod.c,v 1.32 2005/02/12 00:17:09 lindak Exp $ |
| */ |
| |
| /* |
| * Copyright 2009 Sun Microsystems, Inc. All rights reserved. |
| * Use is subject to license terms. |
| */ |
| |
| #ifdef DEBUG |
| /* See sys/queue.h */ |
| #define QUEUEDEBUG 1 |
| #endif |
| |
| #include <sys/param.h> |
| #include <sys/systm.h> |
| #include <sys/atomic.h> |
| #include <sys/proc.h> |
| #include <sys/thread.h> |
| #include <sys/file.h> |
| #include <sys/kmem.h> |
| #include <sys/unistd.h> |
| #include <sys/mount.h> |
| #include <sys/vnode.h> |
| #include <sys/types.h> |
| #include <sys/ddi.h> |
| #include <sys/sunddi.h> |
| #include <sys/stream.h> |
| #include <sys/strsun.h> |
| #include <sys/time.h> |
| #include <sys/class.h> |
| #include <sys/disp.h> |
| #include <sys/cmn_err.h> |
| #include <sys/zone.h> |
| #include <sys/sdt.h> |
| |
| #include <netsmb/smb_osdep.h> |
| |
| #include <netsmb/smb.h> |
| #include <netsmb/smb_conn.h> |
| #include <netsmb/smb_rq.h> |
| #include <netsmb/smb_subr.h> |
| #include <netsmb/smb_tran.h> |
| #include <netsmb/smb_trantcp.h> |
| |
| int smb_iod_send_echo(smb_vc_t *); |
| |
| /* |
| * This is set/cleared when smbfs loads/unloads |
| * No locks should be necessary, because smbfs |
| * can't unload until all the mounts are gone. |
| */ |
| static smb_fscb_t *fscb; |
| void |
| smb_fscb_set(smb_fscb_t *cb) |
| { |
| fscb = cb; |
| } |
| |
| static void |
| smb_iod_share_disconnected(smb_share_t *ssp) |
| { |
| |
| smb_share_invalidate(ssp); |
| |
| /* smbfs_dead() */ |
| if (fscb && fscb->fscb_disconn) { |
| fscb->fscb_disconn(ssp); |
| } |
| } |
| |
| /* |
| * State changes are important and infrequent. |
| * Make them easily observable via dtrace. |
| */ |
| void |
| smb_iod_newstate(struct smb_vc *vcp, int state) |
| { |
| vcp->vc_state = state; |
| } |
| |
| /* Lock Held version of the next function. */ |
| static inline void |
| smb_iod_rqprocessed_LH( |
| struct smb_rq *rqp, |
| int error, |
| int flags) |
| { |
| rqp->sr_flags |= flags; |
| rqp->sr_lerror = error; |
| rqp->sr_rpgen++; |
| rqp->sr_state = SMBRQ_NOTIFIED; |
| cv_broadcast(&rqp->sr_cond); |
| } |
| |
| static void |
| smb_iod_rqprocessed( |
| struct smb_rq *rqp, |
| int error, |
| int flags) |
| { |
| |
| SMBRQ_LOCK(rqp); |
| smb_iod_rqprocessed_LH(rqp, error, flags); |
| SMBRQ_UNLOCK(rqp); |
| } |
| |
| static void |
| smb_iod_invrq(struct smb_vc *vcp) |
| { |
| struct smb_rq *rqp; |
| |
| /* |
| * Invalidate all outstanding requests for this connection |
| */ |
| rw_enter(&vcp->iod_rqlock, RW_READER); |
| TAILQ_FOREACH(rqp, &vcp->iod_rqlist, sr_link) { |
| smb_iod_rqprocessed(rqp, ENOTCONN, SMBR_RESTART); |
| } |
| rw_exit(&vcp->iod_rqlock); |
| } |
| |
| /* |
| * Called by smb_vc_rele, smb_vc_kill, and by the driver |
| * close entry point if the IOD closes its dev handle. |
| * |
| * Forcibly kill the connection and IOD. |
| */ |
| void |
| smb_iod_disconnect(struct smb_vc *vcp) |
| { |
| |
| /* |
| * Inform everyone of the state change. |
| */ |
| SMB_VC_LOCK(vcp); |
| if (vcp->vc_state != SMBIOD_ST_DEAD) { |
| smb_iod_newstate(vcp, SMBIOD_ST_DEAD); |
| cv_broadcast(&vcp->vc_statechg); |
| } |
| SMB_VC_UNLOCK(vcp); |
| |
| /* |
| * Let's be safe here and avoid doing any |
| * call across the network while trying to |
| * shut things down. If we just disconnect, |
| * the server will take care of the logoff. |
| */ |
| SMB_TRAN_DISCONNECT(vcp); |
| } |
| |
| /* |
| * Send one request. |
| * |
| * Called by _addrq (for internal requests) |
| * and _sendall (via _addrq, _multirq, _waitrq) |
| */ |
| static int |
| smb_iod_sendrq(struct smb_rq *rqp) |
| { |
| struct smb_vc *vcp = rqp->sr_vc; |
| mblk_t *m; |
| int error; |
| |
| ASSERT(vcp); |
| ASSERT(SEMA_HELD(&vcp->vc_sendlock)); |
| ASSERT(RW_READ_HELD(&vcp->iod_rqlock)); |
| |
| /* |
| * Note: Anything special for SMBR_INTERNAL here? |
| */ |
| if (vcp->vc_state != SMBIOD_ST_VCACTIVE) { |
| SMBIODEBUG("bad vc_state=%d\n", vcp->vc_state); |
| return (ENOTCONN); |
| } |
| |
| |
| /* |
| * On the first send, set the MID and (maybe) |
| * the signing sequence numbers. The increments |
| * here are serialized by vc_sendlock |
| */ |
| if (rqp->sr_sendcnt == 0) { |
| |
| rqp->sr_mid = vcp->vc_next_mid++; |
| |
| if (rqp->sr_rqflags2 & SMB_FLAGS2_SECURITY_SIGNATURE) { |
| /* |
| * We're signing requests and verifying |
| * signatures on responses. Set the |
| * sequence numbers of the request and |
| * response here, used in smb_rq_verify. |
| */ |
| rqp->sr_seqno = vcp->vc_next_seq++; |
| rqp->sr_rseqno = vcp->vc_next_seq++; |
| } |
| |
| /* Fill in UID, TID, MID, etc. */ |
| smb_rq_fillhdr(rqp); |
| |
| /* |
| * Sign the message now that we're finally done |
| * filling in the SMB header fields, etc. |
| */ |
| if (rqp->sr_rqflags2 & SMB_FLAGS2_SECURITY_SIGNATURE) { |
| smb_rq_sign(rqp); |
| } |
| } |
| if (rqp->sr_sendcnt++ >= 60/SMBSBTIMO) { /* one minute */ |
| smb_iod_rqprocessed(rqp, rqp->sr_lerror, SMBR_RESTART); |
| /* |
| * If all attempts to send a request failed, then |
| * something is seriously hosed. |
| */ |
| return (ENOTCONN); |
| } |
| |
| /* |
| * Replaced m_copym() with Solaris copymsg() which does the same |
| * work when we want to do a M_COPYALL. |
| * m = m_copym(rqp->sr_rq.mb_top, 0, M_COPYALL, 0); |
| */ |
| m = copymsg(rqp->sr_rq.mb_top); |
| |
| #ifdef DTRACE_PROBE |
| DTRACE_PROBE2(smb_iod_sendrq, |
| (smb_rq_t *), rqp, (mblk_t *), m); |
| #else |
| SMBIODEBUG("M:%04x, P:%04x, U:%04x, T:%04x\n", rqp->sr_mid, 0, 0, 0); |
| #endif |
| m_dumpm(m); |
| |
| if (m != NULL) { |
| error = SMB_TRAN_SEND(vcp, m); |
| m = 0; /* consumed by SEND */ |
| } else |
| error = ENOBUFS; |
| |
| rqp->sr_lerror = error; |
| if (error == 0) { |
| SMBRQ_LOCK(rqp); |
| rqp->sr_flags |= SMBR_SENT; |
| rqp->sr_state = SMBRQ_SENT; |
| if (rqp->sr_flags & SMBR_SENDWAIT) |
| cv_broadcast(&rqp->sr_cond); |
| SMBRQ_UNLOCK(rqp); |
| return (0); |
| } |
| /* |
| * Check for fatal errors |
| */ |
| if (SMB_TRAN_FATAL(vcp, error)) { |
| /* |
| * No further attempts should be made |
| */ |
| SMBSDEBUG("TRAN_SEND returned fatal error %d\n", error); |
| return (ENOTCONN); |
| } |
| if (error) |
| SMBSDEBUG("TRAN_SEND returned non-fatal error %d\n", error); |
| |
| #ifdef APPLE |
| /* If proc waiting on rqp was signaled... */ |
| if (smb_rq_intr(rqp)) |
| smb_iod_rqprocessed(rqp, EINTR, 0); |
| #endif |
| |
| return (0); |
| } |
| |
| static int |
| smb_iod_recv1(struct smb_vc *vcp, mblk_t **mpp) |
| { |
| mblk_t *m; |
| uchar_t *hp; |
| int error; |
| |
| top: |
| m = NULL; |
| error = SMB_TRAN_RECV(vcp, &m); |
| if (error == EAGAIN) |
| goto top; |
| if (error) |
| return (error); |
| ASSERT(m); |
| |
| m = m_pullup(m, SMB_HDRLEN); |
| if (m == NULL) { |
| return (ENOSR); |
| } |
| |
| /* |
| * Check the SMB header |
| */ |
| hp = mtod(m, uchar_t *); |
| if (bcmp(hp, SMB_SIGNATURE, SMB_SIGLEN) != 0) { |
| m_freem(m); |
| return (EPROTO); |
| } |
| |
| *mpp = m; |
| return (0); |
| } |
| |
| /* |
| * Process incoming packets |
| * |
| * This is the "reader" loop, run by the IOD thread |
| * while in state SMBIOD_ST_VCACTIVE. The loop now |
| * simply blocks in the socket recv until either a |
| * message arrives, or a disconnect. |
| * |
| * Any non-zero error means the IOD should terminate. |
| */ |
| int |
| smb_iod_recvall(struct smb_vc *vcp) |
| { |
| struct smb_rq *rqp; |
| mblk_t *m; |
| uchar_t *hp; |
| ushort_t mid; |
| int error = 0; |
| int etime_count = 0; /* for "server not responding", etc. */ |
| |
| for (;;) { |
| /* |
| * Check whether someone "killed" this VC, |
| * or is asking the IOD to terminate. |
| */ |
| |
| if (vcp->vc_state != SMBIOD_ST_VCACTIVE) { |
| SMBIODEBUG("bad vc_state=%d\n", vcp->vc_state); |
| error = 0; |
| break; |
| } |
| |
| if (vcp->iod_flags & SMBIOD_SHUTDOWN) { |
| SMBIODEBUG("SHUTDOWN set\n"); |
| /* This IOD thread will terminate. */ |
| SMB_VC_LOCK(vcp); |
| smb_iod_newstate(vcp, SMBIOD_ST_DEAD); |
| cv_broadcast(&vcp->vc_statechg); |
| SMB_VC_UNLOCK(vcp); |
| error = EINTR; |
| break; |
| } |
| |
| m = NULL; |
| error = smb_iod_recv1(vcp, &m); |
| |
| if (error == ETIME && |
| vcp->iod_rqlist.tqh_first != NULL) { |
| /* |
| * Nothing received for 15 seconds and |
| * we have requests in the queue. |
| */ |
| etime_count++; |
| |
| /* |
| * Once, at 15 sec. notify callbacks |
| * and print the warning message. |
| */ |
| if (etime_count == 1) { |
| /* Was: smb_iod_notify_down(vcp); */ |
| if (fscb && fscb->fscb_down) |
| smb_vc_walkshares(vcp, |
| fscb->fscb_down); |
| zprintf(vcp->vc_zoneid, |
| "SMB server %s not responding\n", |
| vcp->vc_srvname); |
| } |
| |
| /* |
| * At 30 sec. try sending an echo, and then |
| * once a minute thereafter. |
| */ |
| if ((etime_count & 3) == 2) { |
| (void) smb_iod_send_echo(vcp); |
| } |
| |
| continue; |
| } /* ETIME && requests in queue */ |
| |
| if (error == ETIME) { |
| /* |
| * If the IOD thread holds the last reference |
| * to this VC, let the IOD thread terminate. |
| */ |
| if (vcp->vc_co.co_usecount > 1) |
| continue; |
| SMB_VC_LOCK(vcp); |
| if (vcp->vc_co.co_usecount == 1) { |
| smb_iod_newstate(vcp, SMBIOD_ST_DEAD); |
| SMB_VC_UNLOCK(vcp); |
| error = 0; |
| break; |
| } |
| SMB_VC_UNLOCK(vcp); |
| continue; |
| } /* error == ETIME */ |
| |
| if (error) { |
| /* |
| * The recv. above returned some error |
| * we can't continue from i.e. ENOTCONN. |
| * It's dangerous to continue here. |
| * (possible infinite loop!) |
| * |
| * If we have requests enqueued, next |
| * state is reconnecting, else idle. |
| */ |
| int state; |
| SMB_VC_LOCK(vcp); |
| state = (vcp->iod_rqlist.tqh_first != NULL) ? |
| SMBIOD_ST_RECONNECT : SMBIOD_ST_IDLE; |
| smb_iod_newstate(vcp, state); |
| cv_broadcast(&vcp->vc_statechg); |
| SMB_VC_UNLOCK(vcp); |
| error = 0; |
| break; |
| } |
| |
| /* |
| * Received something. Yea! |
| */ |
| if (etime_count) { |
| etime_count = 0; |
| |
| zprintf(vcp->vc_zoneid, "SMB server %s OK\n", |
| vcp->vc_srvname); |
| |
| /* Was: smb_iod_notify_up(vcp); */ |
| if (fscb && fscb->fscb_up) |
| smb_vc_walkshares(vcp, fscb->fscb_up); |
| } |
| |
| /* |
| * Have an SMB packet. The SMB header was |
| * checked in smb_iod_recv1(). |
| * Find the request... |
| */ |
| hp = mtod(m, uchar_t *); |
| /*LINTED*/ |
| mid = letohs(SMB_HDRMID(hp)); |
| SMBIODEBUG("mid %04x\n", (uint_t)mid); |
| |
| rw_enter(&vcp->iod_rqlock, RW_READER); |
| TAILQ_FOREACH(rqp, &vcp->iod_rqlist, sr_link) { |
| |
| if (rqp->sr_mid != mid) |
| continue; |
| |
| DTRACE_PROBE2(smb_iod_recvrq, |
| (smb_rq_t *), rqp, (mblk_t *), m); |
| m_dumpm(m); |
| |
| SMBRQ_LOCK(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_UNLOCK(rqp); |
| SMBSDEBUG("duplicate response %d " |
| "(ignored)\n", mid); |
| break; |
| } |
| } |
| smb_iod_rqprocessed_LH(rqp, 0, 0); |
| SMBRQ_UNLOCK(rqp); |
| break; |
| } |
| |
| if (rqp == NULL) { |
| int cmd = SMB_HDRCMD(hp); |
| |
| if (cmd != SMB_COM_ECHO) |
| SMBSDEBUG("drop resp: mid %d, cmd %d\n", |
| (uint_t)mid, cmd); |
| /* smb_printrqlist(vcp); */ |
| m_freem(m); |
| } |
| rw_exit(&vcp->iod_rqlock); |
| |
| } |
| |
| return (error); |
| } |
| |
| /* |
| * The IOD receiver thread has requests pending and |
| * has not received anything in a while. Try to |
| * send an SMB echo request. It's tricky to do a |
| * send from the IOD thread because we can't block. |
| * |
| * Using tmo=SMBNOREPLYWAIT in the request |
| * so smb_rq_reply will skip smb_iod_waitrq. |
| * The smb_smb_echo call uses SMBR_INTERNAL |
| * to avoid calling smb_iod_sendall(). |
| */ |
| int |
| smb_iod_send_echo(smb_vc_t *vcp) |
| { |
| smb_cred_t scred; |
| int err; |
| |
| smb_credinit(&scred, NULL); |
| err = smb_smb_echo(vcp, &scred, SMBNOREPLYWAIT); |
| smb_credrele(&scred); |
| return (err); |
| } |
| |
| /* |
| * The IOD thread is now just a "reader", |
| * so no more smb_iod_request(). Yea! |
| */ |
| |
| /* |
| * Place request in the queue, and send it now if possible. |
| * Called with no locks held. |
| */ |
| int |
| smb_iod_addrq(struct smb_rq *rqp) |
| { |
| struct smb_vc *vcp = rqp->sr_vc; |
| int error, save_newrq; |
| |
| ASSERT(rqp->sr_cred); |
| |
| /* |
| * State should be correct after the check in |
| * smb_rq_enqueue(), but we dropped locks... |
| */ |
| if (vcp->vc_state != SMBIOD_ST_VCACTIVE) { |
| SMBIODEBUG("bad vc_state=%d\n", vcp->vc_state); |
| return (ENOTCONN); |
| } |
| |
| /* |
| * Requests from the IOD itself are marked _INTERNAL, |
| * and get some special treatment to avoid blocking |
| * the reader thread (so we don't deadlock). |
| * The request is not yet on the queue, so we can |
| * modify it's state here without locks. |
| * Only thing using this now is ECHO. |
| */ |
| rqp->sr_owner = curthread; |
| if (rqp->sr_owner == vcp->iod_thr) { |
| rqp->sr_flags |= SMBR_INTERNAL; |
| |
| /* |
| * This is a request from the IOD thread. |
| * Always send directly from this thread. |
| * Note lock order: iod_rqlist, vc_sendlock |
| */ |
| rw_enter(&vcp->iod_rqlock, RW_WRITER); |
| TAILQ_INSERT_HEAD(&vcp->iod_rqlist, rqp, sr_link); |
| rw_downgrade(&vcp->iod_rqlock); |
| |
| /* |
| * Note: iod_sendrq expects vc_sendlock, |
| * so take that here, but carefully: |
| * Never block the IOD thread here. |
| */ |
| if (sema_tryp(&vcp->vc_sendlock) == 0) { |
| SMBIODEBUG("sendlock busy\n"); |
| error = EAGAIN; |
| } else { |
| /* Have vc_sendlock */ |
| error = smb_iod_sendrq(rqp); |
| sema_v(&vcp->vc_sendlock); |
| } |
| |
| rw_exit(&vcp->iod_rqlock); |
| |
| /* |
| * In the non-error case, _removerq |
| * is done by either smb_rq_reply |
| * or smb_iod_waitrq. |
| */ |
| if (error) |
| smb_iod_removerq(rqp); |
| |
| return (error); |
| } |
| |
| rw_enter(&vcp->iod_rqlock, RW_WRITER); |
| |
| TAILQ_INSERT_TAIL(&vcp->iod_rqlist, rqp, sr_link); |
| /* iod_rqlock/WRITER protects iod_newrq */ |
| save_newrq = vcp->iod_newrq; |
| vcp->iod_newrq++; |
| |
| rw_exit(&vcp->iod_rqlock); |
| |
| /* |
| * Now send any requests that need to be sent, |
| * including the one we just put on the list. |
| * Only the thread that found iod_newrq==0 |
| * needs to run the send loop. |
| */ |
| if (save_newrq == 0) |
| smb_iod_sendall(vcp); |
| |
| return (0); |
| } |
| |
| /* |
| * Mark an SMBR_MULTIPACKET request as |
| * needing another send. Similar to the |
| * "normal" part of smb_iod_addrq. |
| */ |
| int |
| smb_iod_multirq(struct smb_rq *rqp) |
| { |
| struct smb_vc *vcp = rqp->sr_vc; |
| int save_newrq; |
| |
| ASSERT(rqp->sr_flags & SMBR_MULTIPACKET); |
| |
| if (rqp->sr_flags & SMBR_INTERNAL) |
| return (EINVAL); |
| |
| if (vcp->vc_state != SMBIOD_ST_VCACTIVE) { |
| SMBIODEBUG("bad vc_state=%d\n", vcp->vc_state); |
| return (ENOTCONN); |
| } |
| |
| rw_enter(&vcp->iod_rqlock, RW_WRITER); |
| |
| /* Already on iod_rqlist, just reset state. */ |
| rqp->sr_state = SMBRQ_NOTSENT; |
| |
| /* iod_rqlock/WRITER protects iod_newrq */ |
| save_newrq = vcp->iod_newrq; |
| vcp->iod_newrq++; |
| |
| rw_exit(&vcp->iod_rqlock); |
| |
| /* |
| * Now send any requests that need to be sent, |
| * including the one we just marked NOTSENT. |
| * Only the thread that found iod_newrq==0 |
| * needs to run the send loop. |
| */ |
| if (save_newrq == 0) |
| smb_iod_sendall(vcp); |
| |
| return (0); |
| } |
| |
| |
| void |
| smb_iod_removerq(struct smb_rq *rqp) |
| { |
| struct smb_vc *vcp = rqp->sr_vc; |
| |
| rw_enter(&vcp->iod_rqlock, RW_WRITER); |
| #ifdef QUEUEDEBUG |
| /* |
| * Make sure we have not already removed it. |
| * See sys/queue.h QUEUEDEBUG_TAILQ_POSTREMOVE |
| * XXX: Don't like the constant 1 here... |
| */ |
| ASSERT(rqp->sr_link.tqe_next != (void *)1L); |
| #endif |
| TAILQ_REMOVE(&vcp->iod_rqlist, rqp, sr_link); |
| rw_exit(&vcp->iod_rqlock); |
| } |
| |
| |
| |
| /* |
| * Wait for a request to complete. |
| * |
| * For normal requests, we need to deal with |
| * ioc_muxcnt dropping below vc_maxmux by |
| * making arrangements to send more... |
| */ |
| int |
| smb_iod_waitrq(struct smb_rq *rqp) |
| { |
| struct smb_vc *vcp = rqp->sr_vc; |
| clock_t tr, tmo1, tmo2; |
| int error, rc; |
| |
| if (rqp->sr_flags & SMBR_INTERNAL) { |
| ASSERT((rqp->sr_flags & SMBR_MULTIPACKET) == 0); |
| smb_iod_removerq(rqp); |
| return (EAGAIN); |
| } |
| |
| /* |
| * Make sure this is NOT the IOD thread, |
| * or the wait below will stop the reader. |
| */ |
| ASSERT(curthread != vcp->iod_thr); |
| |
| SMBRQ_LOCK(rqp); |
| |
| /* |
| * First, wait for the request to be sent. Normally the send |
| * has already happened by the time we get here. However, if |
| * we have more than maxmux entries in the request list, our |
| * request may not be sent until other requests complete. |
| * The wait in this case is due to local I/O demands, so |
| * we don't want the server response timeout to apply. |
| * |
| * If a request is allowed to interrupt this wait, then the |
| * request is cancelled and never sent OTW. Some kinds of |
| * requests should never be cancelled (i.e. close) and those |
| * are marked SMBR_NOINTR_SEND so they either go eventually, |
| * or a connection close will terminate them with ENOTCONN. |
| */ |
| while (rqp->sr_state == SMBRQ_NOTSENT) { |
| rqp->sr_flags |= SMBR_SENDWAIT; |
| if (rqp->sr_flags & SMBR_NOINTR_SEND) { |
| cv_wait(&rqp->sr_cond, &rqp->sr_lock); |
| rc = 1; |
| } else |
| rc = cv_wait_sig(&rqp->sr_cond, &rqp->sr_lock); |
| rqp->sr_flags &= ~SMBR_SENDWAIT; |
| if (rc == 0) { |
| SMBIODEBUG("EINTR in sendwait, rqp=%p\n", rqp); |
| error = EINTR; |
| goto out; |
| } |
| } |
| |
| /* |
| * The request has been sent. Now wait for the response, |
| * with the timeout specified for this request. |
| * Compute all the deadlines now, so we effectively |
| * start the timer(s) after the request is sent. |
| */ |
| if (smb_timo_notice && (smb_timo_notice < rqp->sr_timo)) |
| tmo1 = SEC_TO_TICK(smb_timo_notice); |
| else |
| tmo1 = 0; |
| tmo2 = ddi_get_lbolt() + SEC_TO_TICK(rqp->sr_timo); |
| |
| /* |
| * As above, we don't want to allow interrupt for some |
| * requests like open, because we could miss a succesful |
| * response and therefore "leak" a FID. Such requests |
| * are marked SMBR_NOINTR_RECV to prevent that. |
| * |
| * If "slow server" warnings are enabled, wait first |
| * for the "notice" timeout, and warn if expired. |
| */ |
| if (tmo1 && rqp->sr_rpgen == rqp->sr_rplast) { |
| if (rqp->sr_flags & SMBR_NOINTR_RECV) |
| tr = cv_reltimedwait(&rqp->sr_cond, |
| &rqp->sr_lock, tmo1, TR_CLOCK_TICK); |
| else |
| tr = cv_reltimedwait_sig(&rqp->sr_cond, |
| &rqp->sr_lock, tmo1, TR_CLOCK_TICK); |
| if (tr == 0) { |
| error = EINTR; |
| goto out; |
| } |
| if (tr < 0) { |
| #ifdef DTRACE_PROBE |
| DTRACE_PROBE1(smb_iod_waitrq1, |
| (smb_rq_t *), rqp); |
| #endif |
| #ifdef NOT_YET |
| /* Want this to go ONLY to the user. */ |
| uprintf("SMB server %s has not responded" |
| " to request %d after %d seconds..." |
| " (still waiting).\n", vcp->vc_srvname, |
| rqp->sr_mid, smb_timo_notice); |
| #endif |
| } |
| } |
| |
| /* |
| * Keep waiting until tmo2 is expired. |
| */ |
| while (rqp->sr_rpgen == rqp->sr_rplast) { |
| if (rqp->sr_flags & SMBR_NOINTR_RECV) |
| tr = cv_timedwait(&rqp->sr_cond, |
| &rqp->sr_lock, tmo2); |
| else |
| tr = cv_timedwait_sig(&rqp->sr_cond, |
| &rqp->sr_lock, tmo2); |
| if (tr == 0) { |
| error = EINTR; |
| goto out; |
| } |
| if (tr < 0) { |
| #ifdef DTRACE_PROBE |
| DTRACE_PROBE1(smb_iod_waitrq2, |
| (smb_rq_t *), rqp); |
| #endif |
| #ifdef NOT_YET |
| /* Want this to go ONLY to the user. */ |
| uprintf("SMB server %s has not responded" |
| " to request %d after %d seconds..." |
| " (giving up).\n", vcp->vc_srvname, |
| rqp->sr_mid, rqp->sr_timo); |
| #endif |
| error = ETIME; |
| goto out; |
| } |
| /* got wakeup */ |
| } |
| error = rqp->sr_lerror; |
| rqp->sr_rplast++; |
| |
| out: |
| SMBRQ_UNLOCK(rqp); |
| |
| /* |
| * MULTIPACKET request must stay in the list. |
| * They may need additional responses. |
| */ |
| if ((rqp->sr_flags & SMBR_MULTIPACKET) == 0) |
| smb_iod_removerq(rqp); |
| |
| /* |
| * Some request has been completed. |
| * If we reached the mux limit, |
| * re-run the send loop... |
| */ |
| if (vcp->iod_muxfull) |
| smb_iod_sendall(vcp); |
| |
| return (error); |
| } |
| |
| /* |
| * Shutdown all outstanding I/O requests on the specified share with |
| * ENXIO; used when unmounting a share. (There shouldn't be any for a |
| * non-forced unmount; if this is a forced unmount, we have to shutdown |
| * the requests as part of the unmount process.) |
| */ |
| void |
| smb_iod_shutdown_share(struct smb_share *ssp) |
| { |
| struct smb_vc *vcp = SSTOVC(ssp); |
| struct smb_rq *rqp; |
| |
| /* |
| * Loop through the list of requests and shutdown the ones |
| * that are for the specified share. |
| */ |
| rw_enter(&vcp->iod_rqlock, RW_READER); |
| TAILQ_FOREACH(rqp, &vcp->iod_rqlist, sr_link) { |
| if (rqp->sr_state != SMBRQ_NOTIFIED && rqp->sr_share == ssp) |
| smb_iod_rqprocessed(rqp, EIO, 0); |
| } |
| rw_exit(&vcp->iod_rqlock); |
| } |
| |
| /* |
| * Send all requests that need sending. |
| * Called from _addrq, _multirq, _waitrq |
| */ |
| void |
| smb_iod_sendall(smb_vc_t *vcp) |
| { |
| struct smb_rq *rqp; |
| int error, muxcnt; |
| |
| /* |
| * Clear "newrq" to make sure threads adding |
| * new requests will run this function again. |
| */ |
| rw_enter(&vcp->iod_rqlock, RW_WRITER); |
| vcp->iod_newrq = 0; |
| |
| /* |
| * We only read iod_rqlist, so downgrade rwlock. |
| * This allows the IOD to handle responses while |
| * some requesting thread may be blocked in send. |
| */ |
| rw_downgrade(&vcp->iod_rqlock); |
| |
| /* |
| * Serialize to prevent multiple senders. |
| * Note lock order: iod_rqlock, vc_sendlock |
| */ |
| sema_p(&vcp->vc_sendlock); |
| |
| /* |
| * Walk the list of requests and send when possible. |
| * We avoid having more than vc_maxmux requests |
| * outstanding to the server by traversing only |
| * vc_maxmux entries into this list. Simple! |
| */ |
| ASSERT(vcp->vc_maxmux > 0); |
| error = muxcnt = 0; |
| TAILQ_FOREACH(rqp, &vcp->iod_rqlist, sr_link) { |
| |
| if (vcp->vc_state != SMBIOD_ST_VCACTIVE) { |
| error = ENOTCONN; /* stop everything! */ |
| break; |
| } |
| |
| if (rqp->sr_state == SMBRQ_NOTSENT) { |
| error = smb_iod_sendrq(rqp); |
| if (error) |
| break; |
| } |
| |
| if (++muxcnt == vcp->vc_maxmux) { |
| SMBIODEBUG("muxcnt == vc_maxmux\n"); |
| break; |
| } |
| |
| } |
| |
| /* |
| * If we have vc_maxmux requests outstanding, |
| * arrange for _waitrq to call _sendall as |
| * requests are completed. |
| */ |
| vcp->iod_muxfull = |
| (muxcnt < vcp->vc_maxmux) ? 0 : 1; |
| |
| sema_v(&vcp->vc_sendlock); |
| rw_exit(&vcp->iod_rqlock); |
| } |
| |
| int |
| smb_iod_vc_work(struct smb_vc *vcp, cred_t *cr) |
| { |
| struct file *fp = NULL; |
| int err = 0; |
| |
| /* |
| * This is called by the one-and-only |
| * IOD thread for this VC. |
| */ |
| ASSERT(vcp->iod_thr == curthread); |
| |
| /* |
| * Get the network transport file pointer, |
| * and "loan" it to our transport module. |
| */ |
| if ((fp = getf(vcp->vc_tran_fd)) == NULL) { |
| err = EBADF; |
| goto out; |
| } |
| if ((err = SMB_TRAN_LOAN_FP(vcp, fp, cr)) != 0) |
| goto out; |
| |
| /* |
| * In case of reconnect, tell any enqueued requests |
| * then can GO! |
| */ |
| SMB_VC_LOCK(vcp); |
| vcp->vc_genid++; /* possibly new connection */ |
| smb_iod_newstate(vcp, SMBIOD_ST_VCACTIVE); |
| cv_broadcast(&vcp->vc_statechg); |
| SMB_VC_UNLOCK(vcp); |
| |
| /* |
| * The above cv_broadcast should be sufficient to |
| * get requests going again. |
| * |
| * If we have a callback function, run it. |
| * Was: smb_iod_notify_connected() |
| */ |
| if (fscb && fscb->fscb_connect) |
| smb_vc_walkshares(vcp, fscb->fscb_connect); |
| |
| /* |
| * Run the "reader" loop. |
| */ |
| err = smb_iod_recvall(vcp); |
| |
| /* |
| * The reader loop returned, so we must have a |
| * new state. (disconnected or reconnecting) |
| * |
| * Notify shares of the disconnect. |
| * Was: smb_iod_notify_disconnect() |
| */ |
| smb_vc_walkshares(vcp, smb_iod_share_disconnected); |
| |
| /* |
| * The reader loop function returns only when |
| * there's been an error on the connection, or |
| * this VC has no more references. It also |
| * updates the state before it returns. |
| * |
| * Tell any requests to give up or restart. |
| */ |
| smb_iod_invrq(vcp); |
| |
| out: |
| /* Recall the file descriptor loan. */ |
| (void) SMB_TRAN_LOAN_FP(vcp, NULL, cr); |
| if (fp != NULL) { |
| releasef(vcp->vc_tran_fd); |
| } |
| |
| return (err); |
| } |
| |
| /* |
| * Wait around for someone to ask to use this VC. |
| * If the VC has only the IOD reference, then |
| * wait only a minute or so, then drop it. |
| */ |
| int |
| smb_iod_vc_idle(struct smb_vc *vcp) |
| { |
| clock_t tr, delta = SEC_TO_TICK(15); |
| int err = 0; |
| |
| /* |
| * This is called by the one-and-only |
| * IOD thread for this VC. |
| */ |
| ASSERT(vcp->iod_thr == curthread); |
| |
| SMB_VC_LOCK(vcp); |
| while (vcp->vc_state == SMBIOD_ST_IDLE) { |
| tr = cv_reltimedwait_sig(&vcp->iod_idle, &vcp->vc_lock, |
| delta, TR_CLOCK_TICK); |
| if (tr == 0) { |
| err = EINTR; |
| break; |
| } |
| if (tr < 0) { |
| /* timeout */ |
| if (vcp->vc_co.co_usecount == 1) { |
| /* Let this IOD terminate. */ |
| smb_iod_newstate(vcp, SMBIOD_ST_DEAD); |
| /* nobody to cv_broadcast */ |
| break; |
| } |
| } |
| } |
| SMB_VC_UNLOCK(vcp); |
| |
| return (err); |
| } |
| |
| /* |
| * After a failed reconnect attempt, smbiod will |
| * call this to make current requests error out. |
| */ |
| int |
| smb_iod_vc_rcfail(struct smb_vc *vcp) |
| { |
| clock_t tr; |
| int err = 0; |
| |
| /* |
| * This is called by the one-and-only |
| * IOD thread for this VC. |
| */ |
| ASSERT(vcp->iod_thr == curthread); |
| |
| if (vcp->vc_state != SMBIOD_ST_RECONNECT) |
| return (EINVAL); |
| |
| SMB_VC_LOCK(vcp); |
| |
| smb_iod_newstate(vcp, SMBIOD_ST_RCFAILED); |
| cv_broadcast(&vcp->vc_statechg); |
| |
| /* |
| * Short wait here for two reasons: |
| * (1) Give requests a chance to error out. |
| * (2) Prevent immediate retry. |
| */ |
| tr = cv_reltimedwait_sig(&vcp->iod_idle, &vcp->vc_lock, |
| SEC_TO_TICK(5), TR_CLOCK_TICK); |
| if (tr == 0) |
| err = EINTR; |
| |
| smb_iod_newstate(vcp, SMBIOD_ST_IDLE); |
| cv_broadcast(&vcp->vc_statechg); |
| |
| SMB_VC_UNLOCK(vcp); |
| |
| return (err); |
| } |
| |
| /* |
| * Ask the IOD to reconnect (if not already underway) |
| * then wait for the reconnect to finish. |
| */ |
| int |
| smb_iod_reconnect(struct smb_vc *vcp) |
| { |
| int err = 0, rv; |
| |
| SMB_VC_LOCK(vcp); |
| again: |
| switch (vcp->vc_state) { |
| |
| case SMBIOD_ST_IDLE: |
| smb_iod_newstate(vcp, SMBIOD_ST_RECONNECT); |
| cv_signal(&vcp->iod_idle); |
| /* FALLTHROUGH */ |
| |
| case SMBIOD_ST_RECONNECT: |
| rv = cv_wait_sig(&vcp->vc_statechg, &vcp->vc_lock); |
| if (rv == 0) { |
| err = EINTR; |
| break; |
| } |
| goto again; |
| |
| case SMBIOD_ST_VCACTIVE: |
| err = 0; /* success! */ |
| break; |
| |
| case SMBIOD_ST_RCFAILED: |
| case SMBIOD_ST_DEAD: |
| default: |
| err = ENOTCONN; |
| break; |
| } |
| |
| SMB_VC_UNLOCK(vcp); |
| return (err); |
| } |