smb_iod.c revision 4bff34e37def8a90f9194d81bc345c52ba20086a
/*
* 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
* 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 $
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#ifdef DEBUG
#define QUEUEDEBUG 1
#endif
#ifdef APPLE
#include <sys/smb_apple.h>
#else
#include <netsmb/smb_osdep.h>
#endif
#include <netsmb/smb_conn.h>
#include <netsmb/smb_subr.h>
#include <netsmb/smb_tran.h>
#include <netsmb/smb_trantcp.h>
#ifdef NEED_SMBFS_CALLBACKS
/*
* No locks should be necessary, because smbfs
* can't unload until all the mounts are gone.
*/
static smb_fscb_t *fscb;
int
{
return (0);
}
#endif /* NEED_SMBFS_CALLBACKS */
static void smb_iod_sendall(struct smb_vc *);
static void smb_iod_recvall(struct smb_vc *);
static void smb_iod_main(struct smb_vc *);
#define SMBIOD_SLEEP_TIMO 2
/*
* After this many seconds we want an unresponded-to request to trigger
* some sort of UE (dialogue). If the connection hasn't responded at all
* in this many seconds then the dialogue is of the "connection isn't
* responding would you like to force unmount" variety. If the connection
* has been responding (to other requests that is) then we need a dialogue
* of the "operation is still pending do you want to cancel it" variety.
* At present this latter dialogue does not exist so we have no UE and
* just keep waiting for the slow operation.
*/
/* Lock Held version of the next function. */
static inline void
int error,
int flags)
{
}
static void
int error,
int flags)
{
}
static void
{
/*
* Invalidate all outstanding requests for this connection
*/
}
}
#ifdef SMBTP_UPCALL
static void
{
/* note: called from socket upcall... */
}
#endif
/*
* Called after we fail to send or recv.
* Called with no locks held.
*/
static void
{
#ifdef NEED_SMBFS_CALLBACKS
struct smb_connobj *co;
/*
* Walk the share list, notify...
* Was: smbfs_dead(...share->ss_mount);
* XXX: Ok to hold vc_lock here?
* XXX: More to do here?
*/
/* smbfs_dead() */
}
}
#endif /* NEED_SMBFS_CALLBACKS */
}
int
{
int error;
return (EINVAL);
if (error)
goto errout;
}
#ifdef SMBTP_SELECTID
#endif
#ifdef SMBTP_UPCALL
#endif
if (error) {
SMBIODEBUG("connection to %s error %d\n",
goto errout;
}
/* Success! */
return (0);
return (error);
}
/*
* Called by smb_vc_rele, smb_vc_kill
* Make the connection go away, and
* the IOD (reader) thread too!
*/
int
{
/*
* 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.
*/
#if 0
}
#endif
/*
* Used to call smb_iod_closetran here,
* which did both disconnect and close.
* We now do the close in smb_vc_free,
* so we always have a valid vc_tdata.
* Now just send the disconnect here.
* Extra disconnect calls are ignored.
*/
/*
* If we have an IOD, let it handle the
* state change when it receives the ACK
* from the disconnect we just sent.
* Otherwise set the state here, i.e.
* after failing session setup.
*/
}
return (0);
}
/*
* Send one request.
*
* Called by _addrq (for internal requests)
* and by _sendall (via _addrq, _waitrq)
*/
static int
{
mblk_t *m;
int error;
/*
* Note: requests with sr_flags & SMBR_INTERNAL
* need to pass here with these states:
* SMBIOD_ST_TRANACTIVE: smb_negotiate
* SMBIOD_ST_NEGOACTIVE: smb_ssnsetup
*/
case SMBIOD_ST_NOTCONN:
return (0);
case SMBIOD_ST_DEAD:
/* This is what keeps the iod itself from sending more */
return (0);
case SMBIOD_ST_RECONNECT:
return (0);
default:
break;
}
if (rqp->sr_sendcnt == 0) {
/*
* XXX: Odd place for all this...
* Would expect these values in vc_smbuid
* I think most of this mess is due to having
* the initial UID set to SMB_UID_UKNOWN when
* it should have been initialized to zero!
* REVIST this later. XXX -gwr
*
* This is checking for the case where
* "vc_smbuid" was set to 0 in "smb_smb_ssnsetup()";
* that happens for requests that occur
* after that's done but before we get back the final
* session setup reply, where the latter is what
* gives us the UID. (There can be an arbitrary # of
* session setup packet exchanges to complete
* "extended security" authentication.)
*
* However, if the server gave us a UID of 0 in a
* Session Setup andX reply, and we then do a
* Tree Connect andX and get back a TID, we should
* use that TID, not 0, in subsequent references to
* that tree (e.g., in NetShareEnum RAP requests).
*
* So, for now, we forcibly zero out the TID only if we're
* doing extended security, as that's the only time
* that "vc_smbuid" should be explicitly zeroed.
*
* note we must and do use SMB_TID_UNKNOWN for SMB_COM_ECHO
*/
else
}
/*
* 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);
*/
#ifdef DTRACE_PROBE
#else
#endif
m_dumpm(m);
m = 0; /* consumed by SEND */
if (error == 0) {
return (0);
}
/*
* Check for fatal errors
*/
/*
* No further attempts should be made
*/
return (ENOTCONN);
}
if (error)
#ifdef APPLE
/* If proc waiting on rqp was signaled... */
if (smb_rq_intr(rqp))
#endif
return (0);
}
static int
{
mblk_t *m;
int error;
top:
m = NULL;
goto top;
if (error)
return (error);
ASSERT(m);
m = m_pullup(m, SMB_HDRLEN);
if (m == NULL) {
return (ENOSR);
}
/*
* Check the SMB header
*/
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.
*/
static void
{
mblk_t *m;
int error;
int etime_count = 0; /* for "server not responding", etc. */
for (;;) {
break;
}
SMBIODEBUG("SHUTDOWN set\n");
break;
}
m = NULL;
/*
* Nothing received for 15 seconds,
* and we have requests waiting.
*/
etime_count++;
/*
* Once, at 15 sec. notify callbacks
* and print the warning message.
*/
if (etime_count == 1) {
"SMB server %s not responding\n",
vcp->vc_srvname);
}
/*
* At 30 sec. try sending an echo, and then
* once a minute thereafter. It's tricky to
* do a send from the IOD thread because
* we don't want to block here.
*
* 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().
*/
}
continue;
} /* ETIME && iod_rqwaiting */
/*
* If the IOD thread holds the last reference
* to this VC, disconnect, release, terminate.
* Note, in-line: _vc_kill ... _vc_gone
*/
continue;
continue; /* wait for ACK */
}
continue;
} /* error == ETIME */
if (error) {
/*
* It's dangerous to continue here.
* (possible infinite loop!)
*/
break;
}
/*
* Received something. Yea!
*/
if (etime_count) {
etime_count = 0;
vcp->vc_srvname);
}
/*
* Have an SMB packet. The SMB header was
* checked in smb_iod_recv1().
* Find the request...
*/
/*LINTED*/
continue;
m_dumpm(m);
} else {
} else {
SMBSDEBUG("duplicate response %d "
"(ignored)\n", mid);
break;
}
}
smb_iod_rqprocessed_LH(rqp, 0, 0);
break;
}
if (cmd != SMB_COM_ECHO)
SMBSDEBUG("drop resp: mid %d, cmd %d\n",
/* smb_printrqlist(vcp); */
m_freem(m);
}
}
#ifdef APPLE
/*
* check for interrupts
* On Solaris, handle in smb_iod_waitrq
*/
}
#endif
}
/*
* Looks like we don't need these callbacks,
* but keep the code for now (for Apple).
*/
/*ARGSUSED*/
void
{
#ifdef NEED_SMBFS_CALLBACKS
struct smb_connobj *co;
return;
/*
* Walk the share list, notify...
* Was: smbfs_down(...share->ss_mount);
* XXX: Ok to hold vc_lock here?
*/
/* smbfs_down() */
}
#endif /* NEED_SMBFS_CALLBACKS */
}
/*ARGSUSED*/
void
{
#ifdef NEED_SMBFS_CALLBACKS
struct smb_connobj *co;
return;
/*
* Walk the share list, notify...
* Was: smbfs_up(...share->ss_mount);
* XXX: Ok to hold vc_lock here?
*/
/* smbfs_up() */
}
#endif /* NEED_SMBFS_CALLBACKS */
}
/*
* 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
{
int error, save_newrq;
/* This helps a little with debugging. */
/*
* This is some kind of internal request,
* i.e. negotiate, session setup, echo...
* Allow vc_state < SMBIOD_ST_VCACTIVE, and
* always send directly from this thread.
* May be called by the IOD thread (echo).
* Note lock order: iod_rqlist, vc_sendlock
*/
/*
* Note: iod_sendrq expects vc_sendlock,
* so take that here, but carefully:
* Never block the IOD thread here.
*/
SMBIODEBUG("sendlock busy\n");
} else {
/* Have vc_sendlock */
}
} else {
}
if (error)
return (error);
}
/*
* Normal request from the driver or smbfs.
* State should be correct after the check in
* smb_rq_enqueue(), but we dropped locks...
*/
return (ENOTCONN);
}
/* iod_rqlock/WRITER protects iod_newrq */
/*
* 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)
return (0);
}
/*
* Mark an SMBR_MULTIPACKET request as
* needing another send. Similar to the
* "normal" part of smb_iod_addrq.
*/
int
{
int save_newrq;
return (EINVAL);
return (ENOTCONN);
}
/* Already on iod_rqlist, just reset state. */
/* iod_rqlock/WRITER protects iod_newrq */
/*
* 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)
return (0);
}
int
{
#ifdef QUEUEDEBUG
/*
* Make sure we have not already removed it.
* XXX: Don't like the constant 1 here...
*/
#endif
return (0);
}
/*
* Internal version of smb_iod_waitrq.
*
* This is used when there is no reader thread,
* so we have to do the recv here. The request
* must have the SMBR_INTERNAL flag set.
*/
static int
{
mblk_t *m;
int error;
/* Make sure it's an internal request. */
SMBIODEBUG("not internal\n");
return (EINVAL);
}
/* Only simple requests allowed. */
SMBIODEBUG("multipacket\n");
return (EINVAL);
}
/* Should not already have a response. */
DEBUG_ENTER("smb_iod_waitrq again?\n");
return (0);
}
/*
* The message recv loop. Terminates when we
* receive the message we're looking for.
* Drop others, with complaints.
* Scaled-down version of smb_iod_recvall
*/
for (;;) {
m = NULL;
if (error) {
/*
* It's dangerous to continue here.
* (possible infinite loop!)
*/
#if 0
return (error);
}
continue;
#endif
return (error);
}
/*LINTED*/
SMBIODEBUG("cmd 0x%02x mid %04x\n",
m_dumpm(m);
/*
* Normally, the MID will match.
* For internal requests, also
* match on the cmd to be safe.
*/
break;
SMBIODEBUG("cmd match but not mid!\n");
break;
}
SMBIODEBUG("drop nomatch\n");
m_freem(m);
}
/*
* Have the response we were waiting for.
* Simplified version of the code from
* smb_iod_recvall
*/
} else {
SMBIODEBUG("drop duplicate\n");
m_freem(m);
}
return (0);
}
/*
* Wait for a request to complete.
*
* For internal requests, see smb_iod_waitrq_internal.
* For normal requests, we need to deal with
* ioc_muxcnt dropping below vc_maxmux by
* making arrangements to send more...
*/
int
{
SMBIODEBUG("entry, cmd=0x%02x mid=0x%04x\n",
return (error);
}
/*
* Make sure this is NOT the IOD thread,
* or the wait below will always timeout.
*/
/*
* 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.
*/
rc = 1;
} else
if (rc == 0) {
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.
*/
else
tmo1 = 0;
/*
* 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.
*/
else
if (tr == 0) {
goto out;
}
if (tr < 0) {
#ifdef DTRACE_PROBE
#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..."
#endif
}
}
/*
* Keep waiting until tmo2 is expired.
*/
else
if (tr == 0) {
goto out;
}
if (tr < 0) {
#ifdef DTRACE_PROBE
#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..."
#endif
goto out;
}
/* got wakeup */
}
out:
/*
* MULTIPACKET request must stay in the list.
* They may need additional responses.
*/
/*
* Some request has been completed.
* If we reached the mux limit,
* re-run the send loop...
*/
if (vcp->iod_muxfull)
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
{
/*
* Loop through the list of requests and shutdown the ones
* that are for the specified share.
*/
}
}
/*
* Send all requests that need sending.
* Called from _addrq, _multirq, _waitrq
*/
static void
{
/*
* Clear "newrq" to make sure threads adding
* new requests will run this function again.
*/
/*
* We only read iod_rqlist, so downgrade rwlock.
* This allows the IOD to handle responses while
* some requesting thread may be blocked in send.
*/
/* Expect to find about this many requests. */
/*
* Serialize to prevent multiple senders.
* Note lock order: iod_rqlock, 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!
*/
break;
}
if (error)
break;
}
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 =
}
/*
* "main" function for smbiod daemon thread
*/
void
{
SMBIODEBUG("entry\n");
/*
* Prevent race with thread that created us.
* After we get this lock iod_thr is set.
*/
/* Redundant with iod_thr, but may help debugging. */
/*
* OK, this is a new reader thread.
* In case of reconnect, tell any
* old requests they can restart.
*/
/*
* Run the "reader" loop.
*/
/*
* The reader loop function returns only when
* there's been a fatal error on the connection.
*/
/*
* The reader thread is going away. Clear iod_thr,
* and wake up anybody waiting for us to quit.
*/
/*
* This hold was taken in smb_iod_create()
* when this thread was created.
*/
zthread_exit();
}
/*
* Create the reader thread.
*
* This happens when we are just about to
* enter vc_state = SMBIOD_ST_VCACTIVE;
* See smb_sm_ssnsetup()
*/
int
{
int error;
/*
* Take a hold on the VC for the IOD thread.
* This hold will be released when the IOD
* thread terminates. (or on error below)
*/
SMBIODEBUG("aready have an IOD?");
goto out;
}
/*
* Darwin code used: IOCreateThread(...)
* In Solaris, we use...
*/
NULL, /* stack */
0, /* stack size (default) */
smb_iod_main, /* entry func... */
vcp, /* ... and arg */
0, /* len (of what?) */
minclsyspri); /* priority */
SMBERROR("can't start smbiod\n");
goto out;
}
/* Success! */
error = 0;
out:
if (error)
return (error);
}
/*
* Called from smb_vc_free to do any
* cleanup of our IOD (reader) thread.
*/
int
{
/*
* Let's try to make sure the IOD thread
* goes away, by waiting for it to exit.
* Normally, it's gone by now.
*
* Only wait for a second, because we're in the
* teardown path and don't want to get stuck here.
* Should not take long, or things are hosed...
*/
if (tmo == -1) {
SMBERROR("IOD thread for %s did not exit?\n",
vcp->vc_srvname);
}
}
/* This should not happen. */
SMBIODEBUG("IOD thread did not exit!\n");
/* Try harder? */
}
return (0);
}