Print this page
NEX-6402 SMB2 change notify response wrong when STATUS_NOTIFY_ENUM_DIR
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
NEX-6096 Enable compile warnings re. parentheses in smbsrv
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Rick McNeal <rick.mcneal@nexenta.com>
Reviewed by: Josef 'Jeff' Sipek <josef.sipek@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Jean McCormack <jean.mccormack@nexenta.com>
NEX-3906 Prefer that SMB change notify not tie up a worker thread
NEX-5278 SMB notify should buffer per file handle
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
Reviewed by: Matt Barden <Matt.Barden@nexenta.com>
NEX-4391 improve smb cancel support
Reviewed by: Matt Barden <Matt.Barden@nexenta.com>
NEX-2602 MacOS X Yosemite fails to authenticate
NEX-2107 SMB2 Notify Change fails to return for directory time change
SMB-11 SMB2 message parse & dispatch
SMB-12 SMB2 Negotiate Protocol
SMB-13 SMB2 Session Setup
SMB-14 SMB2 Logoff
SMB-15 SMB2 Tree Connect
SMB-16 SMB2 Tree Disconnect
SMB-17 SMB2 Create
SMB-18 SMB2 Close
SMB-19 SMB2 Flush
SMB-20 SMB2 Read
SMB-21 SMB2 Write
SMB-22 SMB2 Lock/Unlock
SMB-23 SMB2 Ioctl
SMB-24 SMB2 Cancel
SMB-25 SMB2 Echo
SMB-26 SMB2 Query Dir
SMB-27 SMB2 Change Notify
SMB-28 SMB2 Query Info
SMB-29 SMB2 Set Info
SMB-30 SMB2 Oplocks
SMB-53 SMB2 Create Context options
(SMB2 code review cleanup 1, 2, 3)
*** 19,29 ****
* CDDL HEADER END
*/
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
! * Copyright 2013 Nexenta Systems, Inc. All rights reserved.
*/
/*
* File Change Notification (FCN)
* Common parts shared by SMB1 & SMB2
--- 19,29 ----
* CDDL HEADER END
*/
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
! * Copyright 2016 Nexenta Systems, Inc. All rights reserved.
*/
/*
* File Change Notification (FCN)
* Common parts shared by SMB1 & SMB2
*** 83,334 ****
* FILE_ACTION_RENAMED_OLD_NAME 0x00000004
* FILE_ACTION_RENAMED_NEW_NAME 0x00000005
* FILE_ACTION_ADDED_STREAM 0x00000006
* FILE_ACTION_REMOVED_STREAM 0x00000007
* FILE_ACTION_MODIFIED_STREAM 0x00000008
*/
#include <smbsrv/smb_kproto.h>
#include <sys/sdt.h>
! static void smb_notify_sr(smb_request_t *, uint_t, const char *);
! static uint32_t smb_notify_encode_action(struct smb_request *,
! mbuf_chain_t *, uint32_t, char *);
uint32_t
! smb_notify_common(smb_request_t *sr, mbuf_chain_t *mbc,
! uint32_t CompletionFilter)
{
! smb_notify_change_req_t *nc;
smb_node_t *node;
uint32_t status;
- if (sr->fid_ofile == NULL)
- return (NT_STATUS_INVALID_HANDLE);
-
- node = sr->fid_ofile->f_node;
- if (node == NULL || !smb_node_is_dir(node)) {
/*
! * Notify change is only valid on directories.
*/
return (NT_STATUS_INVALID_PARAMETER);
}
/*
! * Prepare to receive event data.
*/
! nc = &sr->sr_ncr;
! nc->nc_flags = CompletionFilter;
! ASSERT(nc->nc_action == 0);
! ASSERT(nc->nc_fname == NULL);
! nc->nc_fname = kmem_zalloc(MAXNAMELEN, KM_SLEEP);
!
/*
! * Subscribe to events on this node.
*/
! smb_node_fcn_subscribe(node, sr);
!
/*
! * Wait for subscribed events to arrive.
! * Expect SMB_REQ_STATE_EVENT_OCCURRED
! * or SMB_REQ_STATE_CANCELED when signaled.
! * Note it's possible (though rare) to already
! * have SMB_REQ_STATE_CANCELED here.
*/
! mutex_enter(&sr->sr_mutex);
! if (sr->sr_state == SMB_REQ_STATE_ACTIVE)
! sr->sr_state = SMB_REQ_STATE_WAITING_EVENT;
! while (sr->sr_state == SMB_REQ_STATE_WAITING_EVENT) {
! cv_wait(&nc->nc_cv, &sr->sr_mutex);
}
- if (sr->sr_state == SMB_REQ_STATE_EVENT_OCCURRED)
- sr->sr_state = SMB_REQ_STATE_ACTIVE;
- mutex_exit(&sr->sr_mutex);
/*
! * Unsubscribe from events on this node.
*/
! smb_node_fcn_unsubscribe(node, sr);
/*
! * Why did we wake up?
*/
switch (sr->sr_state) {
case SMB_REQ_STATE_ACTIVE:
break;
! case SMB_REQ_STATE_CANCELED:
status = NT_STATUS_CANCELLED;
! goto out;
default:
status = NT_STATUS_INTERNAL_ERROR;
! goto out;
}
/*
! * We have SMB_REQ_STATE_ACTIVE.
*
! * If we have event data, marshall it now, else just
! * say "many things changed". Note that when we get
! * action FILE_ACTION_SUBDIR_CHANGED, we don't have
! * any event details and only know that some subdir
! * changed, so just report "many things changed".
*/
! switch (nc->nc_action) {
! case FILE_ACTION_ADDED:
! case FILE_ACTION_REMOVED:
! case FILE_ACTION_MODIFIED:
! case FILE_ACTION_RENAMED_OLD_NAME:
! case FILE_ACTION_RENAMED_NEW_NAME:
! case FILE_ACTION_ADDED_STREAM:
! case FILE_ACTION_REMOVED_STREAM:
! case FILE_ACTION_MODIFIED_STREAM:
/*
! * Build the reply
*/
! status = smb_notify_encode_action(sr, mbc,
! nc->nc_action, nc->nc_fname);
break;
! case FILE_ACTION_SUBDIR_CHANGED:
! status = NT_STATUS_NOTIFY_ENUM_DIR;
break;
-
- case FILE_ACTION_DELETE_PENDING:
- status = NT_STATUS_DELETE_PENDING;
- break;
-
default:
- ASSERT(0);
status = NT_STATUS_INTERNAL_ERROR;
break;
}
! out:
! kmem_free(nc->nc_fname, MAXNAMELEN);
! nc->nc_fname = NULL;
return (status);
}
- /*
- * Encode a FILE_NOTIFY_INFORMATION struct.
- *
- * We only ever put one of these in a response, so this
- * does not bother handling appending additional ones.
- */
static uint32_t
! smb_notify_encode_action(struct smb_request *sr, mbuf_chain_t *mbc,
! uint32_t action, char *fname)
{
! uint32_t namelen;
! ASSERT(FILE_ACTION_ADDED <= action &&
! action <= FILE_ACTION_MODIFIED_STREAM);
! if (fname == NULL)
! return (NT_STATUS_INTERNAL_ERROR);
! namelen = smb_wcequiv_strlen(fname);
! if (namelen == 0)
! return (NT_STATUS_INTERNAL_ERROR);
! if (smb_mbc_encodef(mbc, "%lllU", sr,
! 0, /* NextEntryOffset */
! action, namelen, fname))
! return (NT_STATUS_NOTIFY_ENUM_DIR);
! return (0);
! }
!
! /*
! * smb_notify_file_closed
*
! * Cancel any change-notify calls on this open file.
*/
! void
! smb_notify_file_closed(struct smb_ofile *of)
! {
! smb_session_t *ses;
! smb_request_t *sr;
! smb_slist_t *list;
! SMB_OFILE_VALID(of);
! ses = of->f_session;
! SMB_SESSION_VALID(ses);
! list = &ses->s_req_list;
! smb_slist_enter(list);
! sr = smb_slist_head(list);
! while (sr) {
! SMB_REQ_VALID(sr);
! if (sr->sr_state == SMB_REQ_STATE_WAITING_EVENT &&
! sr->fid_ofile == of) {
! smb_request_cancel(sr);
}
! sr = smb_slist_next(list, sr);
}
! smb_slist_exit(list);
}
/*
! * smb_notify_event
! *
! * Post an event to the watchers on a given node.
! *
! * This makes one exception for RENAME, where we expect a
! * pair of events for the {old,new} directory element names.
! * This only delivers an event for the "new" name.
! *
! * The event delivery mechanism does not implement delivery of
! * multiple events for one "NT Notify" call. One could do that,
! * but modern clients don't actually use the event data. They
! * set a max. received data size of zero, which means we discard
! * the data and send the special "lots changed" error instead.
! * Given that, there's not really any point in implementing the
! * delivery of multiple events. In fact, we don't even need to
! * implement single event delivery, but do so for completeness,
! * for debug convenience, and to be nice to older clients that
! * may actually want some event data instead of the error.
! *
! * Given that we only deliver a single event for an "NT Notify"
! * caller, we want to deliver the "new" name event. (The "old"
! * name event is less important, even ignored by some clients.)
! * Since we know these are delivered in pairs, we can simply
! * discard the "old" name event, knowing that the "new" name
! * event will be delivered immediately afterwards.
! *
! * So, why do event sources post the "old name" event at all?
! * (1) For debugging, so we see both {old,new} names here.
! * (2) If in the future someone decides to implement the
! * delivery of both {old,new} events, the changes can be
! * mostly isolated to this file.
*/
! void
! smb_notify_event(smb_node_t *node, uint_t action, const char *name)
{
! smb_request_t *sr;
! smb_node_fcn_t *fcn;
! SMB_NODE_VALID(node);
! fcn = &node->n_fcn;
! if (action == FILE_ACTION_RENAMED_OLD_NAME)
! return; /* see above */
! mutex_enter(&fcn->fcn_mutex);
!
! sr = list_head(&fcn->fcn_watchers);
! while (sr) {
! smb_notify_sr(sr, action, name);
! sr = list_next(&fcn->fcn_watchers, sr);
}
! mutex_exit(&fcn->fcn_mutex);
}
/*
* What completion filter (masks) apply to each of the
* FILE_ACTION_... events.
*/
static const uint32_t
--- 83,561 ----
* FILE_ACTION_RENAMED_OLD_NAME 0x00000004
* FILE_ACTION_RENAMED_NEW_NAME 0x00000005
* FILE_ACTION_ADDED_STREAM 0x00000006
* FILE_ACTION_REMOVED_STREAM 0x00000007
* FILE_ACTION_MODIFIED_STREAM 0x00000008
+ *
+ * The internal interface between SMB1 and/or SMB2 protocol handlers
+ * and this module has some sophistication to allow for:
+ * (1) code sharing between SMB1 and SMB2(+)
+ * (2) efficient handling of non-blocking scenarios
+ * (3) long blocking calls without tying up a thread
+ *
+ * The interface has three calls (like a three act play)
+ *
+ * smb_notify_act1:
+ * Validate parameters, setup ofile buffer.
+ * If data already available, return it, all done.
+ * (In the "all done" case, skip act2 & act3.)
+ * If no data available, return a special error
+ * ("STATUS_PENDING") to tell the caller they must
+ * proceed with calls to act2 & act3.
+ *
+ * smb_notify_act2:
+ * Arrange wakeup after event delivery or cancellation.
+ * Return leaving the SR with no worker thread.
+ *
+ * smb_notify_act3:
+ * New taskq work thread runs this after the wakeup
+ * or cancellation arranged in act2 happens. This
+ * returns the notification data and retires the SR.
+ *
+ * In the SMB2 notify handler, we call act1 during the initial
+ * synchronous handling of the request. If that returns anything
+ * other than STATUS_PENDING, that request is fully complete.
+ * If act1 returns STATUS_PENDING, SMB2 calls act2 as it's
+ * "go async" handler, which arranges to call act3 later.
+ *
+ * In the SMB1 notify handler there is not separate sync. & async
+ * handler so act1 and (if necessary) act2 are both called during
+ * the initial handling of the request.
+ *
+ * About notify event buffering:
+ *
+ * An important (and poorly documented) feature of SMB notify is
+ * that once a notify call has happened on a given directory handle,
+ * the system CONTINUES to post events to the notify event buffer
+ * for the handle, even when SMB notify calls are NOT running.
+ * When the client next comes back with a notify call, we return
+ * any events that were posted while they were "away". This is
+ * how clients track directory changes without missing events.
+ *
+ * About simultaneous notify calls:
+ *
+ * Note that SMB "notify" calls are destructive to events, much like
+ * reading data from a pipe. It therefore makes little sense to
+ * allow multiple simultaneous callers. However, we permit it
+ * (like Windows does) as follows: When multiple notify calls
+ * are waiting for events, the next event wakes them all, and
+ * only the last one out clears the event buffer. They all get
+ * whatever events are pending at the time they woke up.
+ *
+ * About NT_STATUS_NOTIFY_ENUM_DIR
+ *
+ * One more caution about NT_STATUS_NOTIFY_ENUM_DIR: Some clients
+ * are stupid about re-reading the directory almost continuously when
+ * there are changes happening in the directory. We want to bound
+ * the rate of such directory re-reading, so before returning an
+ * NT_STATUS_NOTIFY_ENUM_DIR, we delay just a little. The length
+ * of the delay can be adjusted via smb_notify_enum_dir_delay,
+ * though it's not expected that should need to be changed.
*/
#include <smbsrv/smb_kproto.h>
#include <sys/sdt.h>
! /*
! * Length of the short delay we impose before returning
! * NT_STATUS_NOTIFY_ENUM_DIR (See above)
! */
! int smb_notify_enum_dir_delay = 100; /* mSec. */
+ static uint32_t smb_notify_get_events(smb_request_t *);
+ static void smb_notify_cancel(smb_request_t *);
+ static void smb_notify_wakeup(smb_request_t *);
+ static void smb_notify_dispatch2(smb_request_t *);
+ static void smb_notify_encode_action(smb_ofile_t *,
+ uint32_t, const char *);
+
+
+ /*
+ * smb_notify_act1()
+ *
+ * Check for events and consume, non-blocking.
+ * Special return STATUS_PENDING means:
+ * No events; caller must call "act2" next.
+ *
+ * See overall design notes, top of file.
+ */
uint32_t
! smb_notify_act1(smb_request_t *sr, uint32_t buflen, uint32_t filter)
{
! smb_ofile_t *of;
smb_node_t *node;
+ smb_notify_t *nc;
uint32_t status;
/*
! * Validate parameters
*/
+ if ((of = sr->fid_ofile) == NULL)
+ return (NT_STATUS_INVALID_HANDLE);
+ nc = &of->f_notify;
+ node = of->f_node;
+ if (node == NULL || !smb_node_is_dir(node)) {
+ /* Notify change is only valid on directories. */
return (NT_STATUS_INVALID_PARAMETER);
}
+ mutex_enter(&of->f_mutex);
+
/*
! * On the first FCN call with this ofile, subscribe to
! * events on the node. The corresponding unsubscribe
! * happens in smb_ofile_delete().
*/
! if (nc->nc_subscribed == B_FALSE) {
! nc->nc_subscribed = B_TRUE;
! smb_node_fcn_subscribe(node);
! /* In case this happened before we subscribed. */
! if (node->flags & NODE_FLAGS_DELETE_ON_CLOSE) {
! nc->nc_events |= FILE_NOTIFY_CHANGE_EV_DELETE;
! }
/*
! * Windows only lets you set these on the first call,
! * so we may as well do the same.
*/
! nc->nc_buffer.max_bytes = buflen;
! nc->nc_filter = filter;
! }
/*
! * If we already have events, consume them.
*/
! sr->raw_data.max_bytes = buflen;
! if (nc->nc_events != 0) {
! status = smb_notify_get_events(sr);
! } else {
! /* Caller will come back for act2 */
! status = NT_STATUS_PENDING;
}
+ mutex_exit(&of->f_mutex);
+
/*
! * See: About NT_STATUS_NOTIFY_ENUM_DIR (above)
*/
! if (status == NT_STATUS_NOTIFY_ENUM_DIR &&
! smb_notify_enum_dir_delay > 0)
! delay(MSEC_TO_TICK(smb_notify_enum_dir_delay));
+ return (status);
+ }
+
+ /*
+ * smb_notify_act2()
+ *
+ * Prepare to wait for events after act1 found that none were pending.
+ * Assume the wait may be for a very long time. (hours, days...)
+ * Special return STATUS_PENDING means the SR will later be
+ * scheduled again on a new worker thread, and this thread
+ * MUST NOT touch it any longer (return SDRC_SR_KEPT).
+ *
+ * See overall design notes, top of file.
+ */
+ uint32_t
+ smb_notify_act2(smb_request_t *sr)
+ {
+ smb_ofile_t *of;
+ smb_notify_t *nc;
+ uint32_t status;
+
/*
! * Sanity checks.
*/
+ if ((of = sr->fid_ofile) == NULL)
+ return (NT_STATUS_INVALID_HANDLE);
+ nc = &of->f_notify;
+
+ mutex_enter(&of->f_mutex);
+
+ /*
+ * Prepare for a potentially long wait for events.
+ * Normally transition from ACTIVE to WAITING_FCN1.
+ *
+ * Note we hold both of->f_mutex, sr->sr_mutex here,
+ * taken in that order.
+ */
+ mutex_enter(&sr->sr_mutex);
switch (sr->sr_state) {
case SMB_REQ_STATE_ACTIVE:
+ /*
+ * This sr has no worker thread until smb_notify_act3
+ * or smb_notify_cancel (later, via taskq_dispatch).
+ */
+ sr->sr_state = SMB_REQ_STATE_WAITING_FCN1;
+ sr->cancel_method = smb_notify_cancel;
+ sr->sr_worker = NULL;
+ list_insert_tail(&nc->nc_waiters, sr);
+ status = NT_STATUS_PENDING;
break;
!
! case SMB_REQ_STATE_CANCELLED:
status = NT_STATUS_CANCELLED;
! break;
default:
status = NT_STATUS_INTERNAL_ERROR;
! break;
}
+ mutex_exit(&sr->sr_mutex);
/*
! * In case we missed any events before setting
! * state FCN1, schedule our own wakeup.
! */
! if (status == NT_STATUS_PENDING && nc->nc_events != 0) {
! smb_notify_wakeup(sr);
! }
!
! mutex_exit(&of->f_mutex);
!
! /* Note: Never NT_STATUS_NOTIFY_ENUM_DIR here. */
! ASSERT(status != NT_STATUS_NOTIFY_ENUM_DIR);
!
! return (status);
! }
!
! /*
! * smb_notify_act3()
*
! * This runs via the 2nd taskq_dispatch call, after we've either
! * seen a change notify event, or the request has been cancelled.
! * Complete it here. This returns to SMB1 or SMB2 code to send
! * the response and free the request.
! *
! * See overall design notes, top of file.
*/
! uint32_t
! smb_notify_act3(smb_request_t *sr)
! {
! smb_ofile_t *of;
! smb_notify_t *nc;
! uint32_t status;
! of = sr->fid_ofile;
! ASSERT(of != NULL);
! nc = &of->f_notify;
!
! mutex_enter(&of->f_mutex);
!
! mutex_enter(&sr->sr_mutex);
! ASSERT3P(sr->sr_worker, ==, NULL);
! sr->sr_worker = curthread;
! sr->cancel_method = NULL;
!
! list_remove(&nc->nc_waiters, sr);
!
! switch (sr->sr_state) {
! case SMB_REQ_STATE_WAITING_FCN2:
/*
! * Got smb_notify_wakeup.
*/
! sr->sr_state = SMB_REQ_STATE_ACTIVE;
! status = 0;
break;
! case SMB_REQ_STATE_CANCEL_PENDING:
! /*
! * Got smb_notify_cancel
! */
! sr->sr_state = SMB_REQ_STATE_CANCELLED;
! status = NT_STATUS_CANCELLED;
break;
default:
status = NT_STATUS_INTERNAL_ERROR;
break;
}
+ mutex_exit(&sr->sr_mutex);
! if (status == 0)
! status = smb_notify_get_events(sr);
!
! mutex_exit(&of->f_mutex);
!
! /*
! * See: About NT_STATUS_NOTIFY_ENUM_DIR (above)
! */
! if (status == NT_STATUS_NOTIFY_ENUM_DIR &&
! smb_notify_enum_dir_delay > 0)
! delay(MSEC_TO_TICK(smb_notify_enum_dir_delay));
!
return (status);
}
static uint32_t
! smb_notify_get_events(smb_request_t *sr)
{
! smb_ofile_t *of;
! smb_notify_t *nc;
! uint32_t status;
! int len;
! of = sr->fid_ofile;
! ASSERT(of != NULL);
! ASSERT(MUTEX_HELD(&of->f_mutex));
! nc = &of->f_notify;
! DTRACE_PROBE2(notify__get__events,
! smb_request_t, sr,
! uint32_t, nc->nc_events);
! /*
! * Special events which override other events
! */
! if (nc->nc_events & FILE_NOTIFY_CHANGE_EV_CLOSED) {
! status = NT_STATUS_NOTIFY_CLEANUP;
! goto out;
! }
! if (nc->nc_events & FILE_NOTIFY_CHANGE_EV_DELETE) {
! status = NT_STATUS_DELETE_PENDING;
! goto out;
! }
! if (nc->nc_events & FILE_NOTIFY_CHANGE_EV_SUBDIR) {
! status = NT_STATUS_NOTIFY_ENUM_DIR;
! goto out;
! }
! if (nc->nc_events & FILE_NOTIFY_CHANGE_EV_OVERFLOW) {
! status = NT_STATUS_NOTIFY_ENUM_DIR;
! goto out;
! }
! /*
! * Normal events (FILE_NOTIFY_VALID_MASK)
*
! * At this point there should be some, or else
! * some sort of bug woke us up for nothing.
*/
! if ((nc->nc_events & FILE_NOTIFY_VALID_MASK) == 0) {
! status = NT_STATUS_INTERNAL_ERROR;
! goto out;
! }
! /*
! * Many Windows clients call change notify with a
! * zero-length buffer, expecting all events to be
! * reported as _ENUM_DIR. Testing max_bytes here
! * because ROOM_FOR check below says "yes" if both
! * max_bytes and the amount we ask for are zero.
! */
! if (nc->nc_buffer.max_bytes <= 0) {
! status = NT_STATUS_NOTIFY_ENUM_DIR;
! goto out;
! }
! /*
! * Client gave us a non-zero output buffer, and
! * there was no overflow event (checked above)
! * so there should be some event data.
! */
! if ((len = nc->nc_buffer.chain_offset) <= 0) {
! status = NT_STATUS_INTERNAL_ERROR;
! goto out;
! }
! /*
! * If the current SR has a smaller output buffer
! * then what was setup by some previous notify,
! * we could have more data than will fit.
! */
! if (!MBC_ROOM_FOR(&sr->raw_data, len)) {
! /* Would overflow caller's buffer. */
! status = NT_STATUS_NOTIFY_ENUM_DIR;
! goto out;
}
!
! /*
! * Copy the event data to sr->raw_data. In the copy,
! * zap the NextEntryOffset in the last entry, and
! * trim any extra bytes at the tail.
! */
! (void) smb_mbc_copy(&sr->raw_data, &nc->nc_buffer, 0, len);
! (void) smb_mbc_poke(&sr->raw_data, nc->nc_last_off, "l", 0);
! smb_mbuf_trim(sr->raw_data.chain, len);
! status = 0;
!
! out:
! /*
! * If there are no other SRs waiting on this ofile,
! * mark all events consumed, except for those that
! * remain until the ofile is closed. That means
! * clear all bits EXCEPT: _EV_CLOSED, _EV_DELETE
! *
! * If there are other waiters (rare) all will get
! * the currently pending events, and then the
! * the last one out will clear the events.
! */
! if (list_is_empty(&nc->nc_waiters)) {
! nc->nc_buffer.chain_offset = 0;
! nc->nc_events &= (FILE_NOTIFY_CHANGE_EV_CLOSED |
! FILE_NOTIFY_CHANGE_EV_DELETE);
}
! return (status);
}
+ /*
+ * Called by common code after a transition from
+ * state WAITING_FCN1 to state CANCEL_PENDING.
+ */
+ static void
+ smb_notify_cancel(smb_request_t *sr)
+ {
+ ASSERT3U(sr->sr_state, ==, SMB_REQ_STATE_CANCEL_PENDING);
+ smb_notify_dispatch2(sr);
+ }
/*
! * Called after ofile event delivery to take a waiting smb request
! * from state FCN1 to state FCN2. This may be called many times
! * (as events are delivered) but it must (exactly once) schedule
! * the taskq job to run smb_notify_act3(). Only the event that
! * takes us from state FCN1 to FCN2 schedules the taskq job.
*/
! static void
! smb_notify_wakeup(smb_request_t *sr)
{
! boolean_t do_disp = B_FALSE;
! SMB_REQ_VALID(sr);
! mutex_enter(&sr->sr_mutex);
! if (sr->sr_state == SMB_REQ_STATE_WAITING_FCN1) {
! sr->sr_state = SMB_REQ_STATE_WAITING_FCN2;
! do_disp = B_TRUE;
! }
! mutex_exit(&sr->sr_mutex);
! if (do_disp) {
! smb_notify_dispatch2(sr);
}
+ }
! /*
! * smb_notify_dispatch2()
! * Schedule a 2nd taskq call to finish up a change notify request;
! * (smb_notify_act3) either completing it or cancelling it.
! */
! static void
! smb_notify_dispatch2(smb_request_t *sr)
! {
! void (*tq_func)(void *);
!
! /*
! * Both of these call smb_notify_act3(), returning
! * to version-specific code to send the response.
! */
! if (sr->session->dialect >= SMB_VERS_2_BASE)
! tq_func = smb2_change_notify_finish;
! else
! tq_func = smb_nt_transact_notify_finish;
!
! (void) taskq_dispatch(sr->sr_server->sv_worker_pool,
! tq_func, sr, TQ_SLEEP);
}
+
/*
* What completion filter (masks) apply to each of the
* FILE_ACTION_... events.
*/
static const uint32_t
*** 369,422 ****
/* FILE_ACTION_MODIFIED_STREAM */
FILE_NOTIFY_CHANGE_STREAM_SIZE |
FILE_NOTIFY_CHANGE_STREAM_WRITE,
/* FILE_ACTION_SUBDIR_CHANGED */
! NODE_FLAGS_WATCH_TREE,
/* FILE_ACTION_DELETE_PENDING */
! NODE_FLAGS_WATCH_TREE |
! FILE_NOTIFY_VALID_MASK,
};
static const int smb_notify_action_nelm =
sizeof (smb_notify_action_mask) /
sizeof (smb_notify_action_mask[0]);
/*
! * smb_notify_sr
*
! * Post an event to an smb request waiting on some node.
! *
! * Note that node->fcn.mutex is held. This implies a
! * lock order: node->fcn.mutex, then sr_mutex
*/
! static void
! smb_notify_sr(smb_request_t *sr, uint_t action, const char *name)
{
! smb_notify_change_req_t *ncr;
! uint32_t mask;
! SMB_REQ_VALID(sr);
! ncr = &sr->sr_ncr;
/*
! * Compute the completion filter mask bits for which
! * we will signal waiting notify requests.
*/
VERIFY(action < smb_notify_action_nelm);
! mask = smb_notify_action_mask[action];
- mutex_enter(&sr->sr_mutex);
- if (sr->sr_state == SMB_REQ_STATE_WAITING_EVENT &&
- (ncr->nc_flags & mask) != 0) {
- sr->sr_state = SMB_REQ_STATE_EVENT_OCCURRED;
/*
! * Save event data in the sr_ncr field so the
! * reply handler can return it.
*/
! ncr->nc_action = action;
! if (name != NULL)
! (void) strlcpy(ncr->nc_fname, name, MAXNAMELEN);
! cv_signal(&ncr->nc_cv);
}
! mutex_exit(&sr->sr_mutex);
}
--- 596,755 ----
/* FILE_ACTION_MODIFIED_STREAM */
FILE_NOTIFY_CHANGE_STREAM_SIZE |
FILE_NOTIFY_CHANGE_STREAM_WRITE,
/* FILE_ACTION_SUBDIR_CHANGED */
! FILE_NOTIFY_CHANGE_EV_SUBDIR,
/* FILE_ACTION_DELETE_PENDING */
! FILE_NOTIFY_CHANGE_EV_DELETE,
!
! /* FILE_ACTION_HANDLE_CLOSED */
! FILE_NOTIFY_CHANGE_EV_CLOSED,
};
static const int smb_notify_action_nelm =
sizeof (smb_notify_action_mask) /
sizeof (smb_notify_action_mask[0]);
/*
! * smb_notify_ofile
*
! * Post an event to the change notify buffer for this ofile,
! * subject to the mask that selects subscribed event types.
! * If an SR is waiting for events and we've delivered some,
! * wake the SR.
*/
! void
! smb_notify_ofile(smb_ofile_t *of, uint_t action, const char *name)
{
! smb_notify_t *nc;
! smb_request_t *sr;
! uint32_t filter, events;
! SMB_OFILE_VALID(of);
+ mutex_enter(&of->f_mutex);
+ nc = &of->f_notify;
+
/*
! * Compute the filter & event bits for this action,
! * which determine whether we'll post the event.
! * Note: always sensitive to: delete, closed.
*/
+ filter = nc->nc_filter |
+ FILE_NOTIFY_CHANGE_EV_DELETE |
+ FILE_NOTIFY_CHANGE_EV_CLOSED;
VERIFY(action < smb_notify_action_nelm);
! events = smb_notify_action_mask[action];
! if ((filter & events) == 0)
! goto unlock_out;
/*
! * OK, we're going to post this event.
*/
! switch (action) {
! case FILE_ACTION_ADDED:
! case FILE_ACTION_REMOVED:
! case FILE_ACTION_MODIFIED:
! case FILE_ACTION_RENAMED_OLD_NAME:
! case FILE_ACTION_RENAMED_NEW_NAME:
! case FILE_ACTION_ADDED_STREAM:
! case FILE_ACTION_REMOVED_STREAM:
! case FILE_ACTION_MODIFIED_STREAM:
! /*
! * Append this event to the buffer.
! * Also keep track of events seen.
! */
! smb_notify_encode_action(of, action, name);
! nc->nc_events |= events;
! break;
!
! case FILE_ACTION_SUBDIR_CHANGED:
! case FILE_ACTION_DELETE_PENDING:
! case FILE_ACTION_HANDLE_CLOSED:
! /*
! * These are "internal" events, and therefore
! * are not appended to the response buffer.
! * Just record the event flags and wakeup.
! */
! nc->nc_events |= events;
! break;
!
! default:
! ASSERT(0); /* bogus action */
! break;
}
!
! sr = list_head(&nc->nc_waiters);
! while (sr != NULL) {
! smb_notify_wakeup(sr);
! sr = list_next(&nc->nc_waiters, sr);
! }
!
! unlock_out:
! mutex_exit(&of->f_mutex);
! }
!
! /*
! * Encode a FILE_NOTIFY_INFORMATION struct.
! */
! static void
! smb_notify_encode_action(smb_ofile_t *of,
! uint32_t action, const char *fname)
! {
! smb_notify_t *nc = &of->f_notify;
! mbuf_chain_t *mbc;
! uint32_t namelen, totlen;
!
! ASSERT(nc != NULL);
! ASSERT(FILE_ACTION_ADDED <= action &&
! action <= FILE_ACTION_MODIFIED_STREAM);
! ASSERT(fname != NULL);
! ASSERT(MUTEX_HELD(&of->f_mutex));
!
! /* Once we've run out of room, stop trying to append. */
! if ((nc->nc_events & FILE_NOTIFY_CHANGE_EV_OVERFLOW) != 0)
! return;
!
! if (fname == NULL)
! return;
! namelen = smb_wcequiv_strlen(fname);
! if (namelen == 0)
! return;
!
! /*
! * Layout is: 3 DWORDS, Unicode string, pad(4).
! */
! mbc = &nc->nc_buffer;
! totlen = (12 + namelen + 3) & ~3;
! if (MBC_ROOM_FOR(mbc, totlen) == 0) {
! nc->nc_events |= FILE_NOTIFY_CHANGE_EV_OVERFLOW;
! return;
! }
!
! /*
! * Keep track of where this entry starts (nc_last_off)
! * because after we put all entries, we need to zap
! * the NextEntryOffset field in the last one.
! */
! nc->nc_last_off = mbc->chain_offset;
!
! /*
! * Encode this entry, then 4-byte alignment padding.
! *
! * Note that smb_mbc_encodef with a "U" code puts a
! * Unicode string with a null termination. We don't
! * want a null, but do want alignment padding. We
! * get that by encoding with "U.." at the end of the
! * encoding string, which gets us two bytes for the
! * Unicode NULL, and two more zeros for the "..".
! * We then "back up" the chain_offset (finger) so it's
! * correctly 4-byte aligned. We will sometimes have
! * written a couple more bytes than needed, but we'll
! * just overwrite those with the next entry. At the
! * end, we trim the mbuf chain to the correct length.
! */
! (void) smb_mbc_encodef(mbc, "lllU..",
! totlen, /* NextEntryOffset */
! action, namelen, fname);
! mbc->chain_offset = nc->nc_last_off + totlen;
}