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)

Split Close
Expand all
Collapse all
          --- old/usr/src/uts/common/fs/smbsrv/smb_notify.c
          +++ new/usr/src/uts/common/fs/smbsrv/smb_notify.c
↓ open down ↓ 13 lines elided ↑ open up ↑
  14   14   * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15   15   * If applicable, add the following below this CDDL HEADER, with the
  16   16   * fields enclosed by brackets "[]" replaced with your own identifying
  17   17   * information: Portions Copyright [yyyy] [name of copyright owner]
  18   18   *
  19   19   * CDDL HEADER END
  20   20   */
  21   21  
  22   22  /*
  23   23   * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
  24      - * Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
       24 + * Copyright 2016 Nexenta Systems, Inc.  All rights reserved.
  25   25   */
  26   26  
  27   27  /*
  28   28   * File Change Notification (FCN)
  29   29   * Common parts shared by SMB1 & SMB2
  30   30   */
  31   31  
  32   32  /*
  33   33   * This command notifies the client when the specified directory
  34   34   * has changed, and optionally returns the names of files and
↓ open down ↓ 43 lines elided ↑ open up ↑
  78   78   * Where Action describes what happened to the file named FileName:
  79   79   *
  80   80   * FILE_ACTION_ADDED            0x00000001
  81   81   * FILE_ACTION_REMOVED          0x00000002
  82   82   * FILE_ACTION_MODIFIED         0x00000003
  83   83   * FILE_ACTION_RENAMED_OLD_NAME 0x00000004
  84   84   * FILE_ACTION_RENAMED_NEW_NAME 0x00000005
  85   85   * FILE_ACTION_ADDED_STREAM     0x00000006
  86   86   * FILE_ACTION_REMOVED_STREAM   0x00000007
  87   87   * FILE_ACTION_MODIFIED_STREAM  0x00000008
       88 + *
       89 + * The internal interface between SMB1 and/or SMB2 protocol handlers
       90 + * and this module has some sophistication to allow for:
       91 + * (1) code sharing between SMB1 and SMB2(+)
       92 + * (2) efficient handling of non-blocking scenarios
       93 + * (3) long blocking calls without tying up a thread
       94 + *
       95 + * The interface has three calls (like a three act play)
       96 + *
       97 + * smb_notify_act1:
       98 + *      Validate parameters, setup ofile buffer.
       99 + *      If data already available, return it, all done.
      100 + *      (In the "all done" case, skip act2 & act3.)
      101 + *      If no data available, return a special error
      102 + *      ("STATUS_PENDING") to tell the caller they must
      103 + *      proceed with calls to act2 & act3.
      104 + *
      105 + * smb_notify_act2:
      106 + *      Arrange wakeup after event delivery or cancellation.
      107 + *      Return leaving the SR with no worker thread.
      108 + *
      109 + * smb_notify_act3:
      110 + *      New taskq work thread runs this after the wakeup
      111 + *      or cancellation arranged in act2 happens.  This
      112 + *      returns the notification data and retires the SR.
      113 + *
      114 + * In the SMB2 notify handler, we call act1 during the initial
      115 + * synchronous handling of the request.  If that returns anything
      116 + * other than STATUS_PENDING, that request is fully complete.
      117 + * If act1 returns STATUS_PENDING, SMB2 calls act2 as it's
      118 + * "go async" handler, which arranges to call act3 later.
      119 + *
      120 + * In the SMB1 notify handler there is not separate sync. & async
      121 + * handler so act1 and (if necessary) act2 are both called during
      122 + * the initial handling of the request.
      123 + *
      124 + * About notify event buffering:
      125 + *
      126 + * An important (and poorly documented) feature of SMB notify is
      127 + * that once a notify call has happened on a given directory handle,
      128 + * the system CONTINUES to post events to the notify event buffer
      129 + * for the handle, even when SMB notify calls are NOT running.
      130 + * When the client next comes back with a notify call, we return
      131 + * any events that were posted while they were "away".  This is
      132 + * how clients track directory changes without missing events.
      133 + *
      134 + * About simultaneous notify calls:
      135 + *
      136 + * Note that SMB "notify" calls are destructive to events, much like
      137 + * reading data from a pipe.  It therefore makes little sense to
      138 + * allow multiple simultaneous callers.  However, we permit it
      139 + * (like Windows does) as follows:  When multiple notify calls
      140 + * are waiting for events, the next event wakes them all, and
      141 + * only the last one out clears the event buffer.  They all get
      142 + * whatever events are pending at the time they woke up.
      143 + *
      144 + * About NT_STATUS_NOTIFY_ENUM_DIR
      145 + *
      146 + * One more caution about NT_STATUS_NOTIFY_ENUM_DIR:  Some clients
      147 + * are stupid about re-reading the directory almost continuously when
      148 + * there are changes happening in the directory.  We want to bound
      149 + * the rate of such directory re-reading, so before returning an
      150 + * NT_STATUS_NOTIFY_ENUM_DIR, we delay just a little.  The length
      151 + * of the delay can be adjusted via smb_notify_enum_dir_delay,
      152 + * though it's not expected that should need to be changed.
  88  153   */
  89  154  
  90  155  #include <smbsrv/smb_kproto.h>
  91  156  #include <sys/sdt.h>
  92  157  
  93      -static void smb_notify_sr(smb_request_t *, uint_t, const char *);
  94      -static uint32_t smb_notify_encode_action(struct smb_request *,
  95      -        mbuf_chain_t *, uint32_t, char *);
      158 +/*
      159 + * Length of the short delay we impose before returning
      160 + * NT_STATUS_NOTIFY_ENUM_DIR (See above)
      161 + */
      162 +int smb_notify_enum_dir_delay = 100; /* mSec. */
  96  163  
      164 +static uint32_t smb_notify_get_events(smb_request_t *);
      165 +static void smb_notify_cancel(smb_request_t *);
      166 +static void smb_notify_wakeup(smb_request_t *);
      167 +static void smb_notify_dispatch2(smb_request_t *);
      168 +static void smb_notify_encode_action(smb_ofile_t *,
      169 +        uint32_t, const char *);
      170 +
      171 +
      172 +/*
      173 + * smb_notify_act1()
      174 + *
      175 + * Check for events and consume, non-blocking.
      176 + * Special return STATUS_PENDING means:
      177 + * No events; caller must call "act2" next.
      178 + *
      179 + * See overall design notes, top of file.
      180 + */
  97  181  uint32_t
  98      -smb_notify_common(smb_request_t *sr, mbuf_chain_t *mbc,
  99      -        uint32_t CompletionFilter)
      182 +smb_notify_act1(smb_request_t *sr, uint32_t buflen, uint32_t filter)
 100  183  {
 101      -        smb_notify_change_req_t *nc;
      184 +        smb_ofile_t     *of;
 102  185          smb_node_t      *node;
      186 +        smb_notify_t    *nc;
 103  187          uint32_t        status;
 104  188  
 105      -        if (sr->fid_ofile == NULL)
      189 +        /*
      190 +         * Validate parameters
      191 +         */
      192 +        if ((of = sr->fid_ofile) == NULL)
 106  193                  return (NT_STATUS_INVALID_HANDLE);
 107      -
 108      -        node = sr->fid_ofile->f_node;
      194 +        nc = &of->f_notify;
      195 +        node = of->f_node;
 109  196          if (node == NULL || !smb_node_is_dir(node)) {
 110      -                /*
 111      -                 * Notify change is only valid on directories.
 112      -                 */
      197 +                /* Notify change is only valid on directories. */
 113  198                  return (NT_STATUS_INVALID_PARAMETER);
 114  199          }
 115  200  
      201 +        mutex_enter(&of->f_mutex);
      202 +
 116  203          /*
 117      -         * Prepare to receive event data.
      204 +         * On the first FCN call with this ofile, subscribe to
      205 +         * events on the node.  The corresponding unsubscribe
      206 +         * happens in smb_ofile_delete().
 118  207           */
 119      -        nc = &sr->sr_ncr;
 120      -        nc->nc_flags = CompletionFilter;
 121      -        ASSERT(nc->nc_action == 0);
 122      -        ASSERT(nc->nc_fname == NULL);
 123      -        nc->nc_fname = kmem_zalloc(MAXNAMELEN, KM_SLEEP);
 124      -
      208 +        if (nc->nc_subscribed == B_FALSE) {
      209 +                nc->nc_subscribed = B_TRUE;
      210 +                smb_node_fcn_subscribe(node);
      211 +                /* In case this happened before we subscribed. */
      212 +                if (node->flags & NODE_FLAGS_DELETE_ON_CLOSE) {
      213 +                        nc->nc_events |= FILE_NOTIFY_CHANGE_EV_DELETE;
      214 +                }
      215 +                /*
      216 +                 * Windows only lets you set these on the first call,
      217 +                 * so we may as well do the same.
      218 +                 */
      219 +                nc->nc_buffer.max_bytes = buflen;
      220 +                nc->nc_filter = filter;
      221 +        }
 125  222          /*
 126      -         * Subscribe to events on this node.
      223 +         * If we already have events, consume them.
 127  224           */
 128      -        smb_node_fcn_subscribe(node, sr);
      225 +        sr->raw_data.max_bytes = buflen;
      226 +        if (nc->nc_events != 0) {
      227 +                status = smb_notify_get_events(sr);
      228 +        } else {
      229 +                /* Caller will come back for act2 */
      230 +                status = NT_STATUS_PENDING;
      231 +        }
 129  232  
      233 +        mutex_exit(&of->f_mutex);
      234 +
 130  235          /*
 131      -         * Wait for subscribed events to arrive.
 132      -         * Expect SMB_REQ_STATE_EVENT_OCCURRED
 133      -         * or SMB_REQ_STATE_CANCELED when signaled.
 134      -         * Note it's possible (though rare) to already
 135      -         * have SMB_REQ_STATE_CANCELED here.
      236 +         * See: About NT_STATUS_NOTIFY_ENUM_DIR (above)
 136  237           */
 137      -        mutex_enter(&sr->sr_mutex);
 138      -        if (sr->sr_state == SMB_REQ_STATE_ACTIVE)
 139      -                sr->sr_state = SMB_REQ_STATE_WAITING_EVENT;
 140      -        while (sr->sr_state == SMB_REQ_STATE_WAITING_EVENT) {
 141      -                cv_wait(&nc->nc_cv, &sr->sr_mutex);
 142      -        }
 143      -        if (sr->sr_state == SMB_REQ_STATE_EVENT_OCCURRED)
 144      -                sr->sr_state = SMB_REQ_STATE_ACTIVE;
 145      -        mutex_exit(&sr->sr_mutex);
      238 +        if (status == NT_STATUS_NOTIFY_ENUM_DIR &&
      239 +            smb_notify_enum_dir_delay > 0)
      240 +                delay(MSEC_TO_TICK(smb_notify_enum_dir_delay));
 146  241  
      242 +        return (status);
      243 +}
      244 +
      245 +/*
      246 + * smb_notify_act2()
      247 + *
      248 + * Prepare to wait for events after act1 found that none were pending.
      249 + * Assume the wait may be for a very long time.  (hours, days...)
      250 + * Special return STATUS_PENDING means the SR will later be
      251 + * scheduled again on a new worker thread, and this thread
      252 + * MUST NOT touch it any longer (return SDRC_SR_KEPT).
      253 + *
      254 + * See overall design notes, top of file.
      255 + */
      256 +uint32_t
      257 +smb_notify_act2(smb_request_t *sr)
      258 +{
      259 +        smb_ofile_t     *of;
      260 +        smb_notify_t    *nc;
      261 +        uint32_t        status;
      262 +
 147  263          /*
 148      -         * Unsubscribe from events on this node.
      264 +         * Sanity checks.
 149  265           */
 150      -        smb_node_fcn_unsubscribe(node, sr);
      266 +        if ((of = sr->fid_ofile) == NULL)
      267 +                return (NT_STATUS_INVALID_HANDLE);
      268 +        nc = &of->f_notify;
 151  269  
      270 +        mutex_enter(&of->f_mutex);
      271 +
 152  272          /*
 153      -         * Why did we wake up?
      273 +         * Prepare for a potentially long wait for events.
      274 +         * Normally transition from ACTIVE to WAITING_FCN1.
      275 +         *
      276 +         * Note we hold both of->f_mutex, sr->sr_mutex here,
      277 +         * taken in that order.
 154  278           */
      279 +        mutex_enter(&sr->sr_mutex);
 155  280          switch (sr->sr_state) {
 156  281          case SMB_REQ_STATE_ACTIVE:
      282 +                /*
      283 +                 * This sr has no worker thread until smb_notify_act3
      284 +                 * or smb_notify_cancel (later, via taskq_dispatch).
      285 +                 */
      286 +                sr->sr_state = SMB_REQ_STATE_WAITING_FCN1;
      287 +                sr->cancel_method = smb_notify_cancel;
      288 +                sr->sr_worker = NULL;
      289 +                list_insert_tail(&nc->nc_waiters, sr);
      290 +                status = NT_STATUS_PENDING;
 157  291                  break;
 158      -        case SMB_REQ_STATE_CANCELED:
      292 +
      293 +        case SMB_REQ_STATE_CANCELLED:
 159  294                  status = NT_STATUS_CANCELLED;
 160      -                goto out;
      295 +                break;
 161  296          default:
 162  297                  status = NT_STATUS_INTERNAL_ERROR;
 163      -                goto out;
      298 +                break;
 164  299          }
      300 +        mutex_exit(&sr->sr_mutex);
 165  301  
 166  302          /*
 167      -         * We have SMB_REQ_STATE_ACTIVE.
 168      -         *
 169      -         * If we have event data, marshall it now, else just
 170      -         * say "many things changed". Note that when we get
 171      -         * action FILE_ACTION_SUBDIR_CHANGED, we don't have
 172      -         * any event details and only know that some subdir
 173      -         * changed, so just report "many things changed".
      303 +         * In case we missed any events before setting
      304 +         * state FCN1, schedule our own wakeup.
 174  305           */
 175      -        switch (nc->nc_action) {
      306 +        if (status == NT_STATUS_PENDING && nc->nc_events != 0) {
      307 +                smb_notify_wakeup(sr);
      308 +        }
 176  309  
 177      -        case FILE_ACTION_ADDED:
 178      -        case FILE_ACTION_REMOVED:
 179      -        case FILE_ACTION_MODIFIED:
 180      -        case FILE_ACTION_RENAMED_OLD_NAME:
 181      -        case FILE_ACTION_RENAMED_NEW_NAME:
 182      -        case FILE_ACTION_ADDED_STREAM:
 183      -        case FILE_ACTION_REMOVED_STREAM:
 184      -        case FILE_ACTION_MODIFIED_STREAM:
      310 +        mutex_exit(&of->f_mutex);
      311 +
      312 +        /* Note: Never NT_STATUS_NOTIFY_ENUM_DIR here. */
      313 +        ASSERT(status != NT_STATUS_NOTIFY_ENUM_DIR);
      314 +
      315 +        return (status);
      316 +}
      317 +
      318 +/*
      319 + * smb_notify_act3()
      320 + *
      321 + * This runs via the 2nd taskq_dispatch call, after we've either
      322 + * seen a change notify event, or the request has been cancelled.
      323 + * Complete it here.  This returns to SMB1 or SMB2 code to send
      324 + * the response and free the request.
      325 + *
      326 + * See overall design notes, top of file.
      327 + */
      328 +uint32_t
      329 +smb_notify_act3(smb_request_t *sr)
      330 +{
      331 +        smb_ofile_t     *of;
      332 +        smb_notify_t    *nc;
      333 +        uint32_t        status;
      334 +
      335 +        of = sr->fid_ofile;
      336 +        ASSERT(of != NULL);
      337 +        nc = &of->f_notify;
      338 +
      339 +        mutex_enter(&of->f_mutex);
      340 +
      341 +        mutex_enter(&sr->sr_mutex);
      342 +        ASSERT3P(sr->sr_worker, ==, NULL);
      343 +        sr->sr_worker = curthread;
      344 +        sr->cancel_method = NULL;
      345 +
      346 +        list_remove(&nc->nc_waiters, sr);
      347 +
      348 +        switch (sr->sr_state) {
      349 +        case SMB_REQ_STATE_WAITING_FCN2:
 185  350                  /*
 186      -                 * Build the reply
      351 +                 * Got smb_notify_wakeup.
 187  352                   */
 188      -                status = smb_notify_encode_action(sr, mbc,
 189      -                    nc->nc_action, nc->nc_fname);
      353 +                sr->sr_state = SMB_REQ_STATE_ACTIVE;
      354 +                status = 0;
 190  355                  break;
 191  356  
 192      -        case FILE_ACTION_SUBDIR_CHANGED:
 193      -                status = NT_STATUS_NOTIFY_ENUM_DIR;
      357 +        case SMB_REQ_STATE_CANCEL_PENDING:
      358 +                /*
      359 +                 * Got smb_notify_cancel
      360 +                 */
      361 +                sr->sr_state = SMB_REQ_STATE_CANCELLED;
      362 +                status = NT_STATUS_CANCELLED;
 194  363                  break;
 195      -
 196      -        case FILE_ACTION_DELETE_PENDING:
 197      -                status = NT_STATUS_DELETE_PENDING;
 198      -                break;
 199      -
 200  364          default:
 201      -                ASSERT(0);
 202  365                  status = NT_STATUS_INTERNAL_ERROR;
 203  366                  break;
 204  367          }
      368 +        mutex_exit(&sr->sr_mutex);
 205  369  
 206      -out:
 207      -        kmem_free(nc->nc_fname, MAXNAMELEN);
 208      -        nc->nc_fname = NULL;
      370 +        if (status == 0)
      371 +                status = smb_notify_get_events(sr);
      372 +
      373 +        mutex_exit(&of->f_mutex);
      374 +
      375 +        /*
      376 +         * See: About NT_STATUS_NOTIFY_ENUM_DIR (above)
      377 +         */
      378 +        if (status == NT_STATUS_NOTIFY_ENUM_DIR &&
      379 +            smb_notify_enum_dir_delay > 0)
      380 +                delay(MSEC_TO_TICK(smb_notify_enum_dir_delay));
      381 +
 209  382          return (status);
 210  383  }
 211  384  
 212      -/*
 213      - * Encode a FILE_NOTIFY_INFORMATION struct.
 214      - *
 215      - * We only ever put one of these in a response, so this
 216      - * does not bother handling appending additional ones.
 217      - */
 218  385  static uint32_t
 219      -smb_notify_encode_action(struct smb_request *sr, mbuf_chain_t *mbc,
 220      -        uint32_t action, char *fname)
      386 +smb_notify_get_events(smb_request_t *sr)
 221  387  {
 222      -        uint32_t namelen;
      388 +        smb_ofile_t     *of;
      389 +        smb_notify_t    *nc;
      390 +        uint32_t        status;
      391 +        int             len;
 223  392  
 224      -        ASSERT(FILE_ACTION_ADDED <= action &&
 225      -            action <= FILE_ACTION_MODIFIED_STREAM);
      393 +        of = sr->fid_ofile;
      394 +        ASSERT(of != NULL);
      395 +        ASSERT(MUTEX_HELD(&of->f_mutex));
      396 +        nc = &of->f_notify;
 226  397  
 227      -        if (fname == NULL)
 228      -                return (NT_STATUS_INTERNAL_ERROR);
 229      -        namelen = smb_wcequiv_strlen(fname);
 230      -        if (namelen == 0)
 231      -                return (NT_STATUS_INTERNAL_ERROR);
      398 +        DTRACE_PROBE2(notify__get__events,
      399 +            smb_request_t, sr,
      400 +            uint32_t, nc->nc_events);
 232  401  
 233      -        if (smb_mbc_encodef(mbc, "%lllU", sr,
 234      -            0, /* NextEntryOffset */
 235      -            action, namelen, fname))
 236      -                return (NT_STATUS_NOTIFY_ENUM_DIR);
      402 +        /*
      403 +         * Special events which override other events
      404 +         */
      405 +        if (nc->nc_events & FILE_NOTIFY_CHANGE_EV_CLOSED) {
      406 +                status = NT_STATUS_NOTIFY_CLEANUP;
      407 +                goto out;
      408 +        }
      409 +        if (nc->nc_events & FILE_NOTIFY_CHANGE_EV_DELETE) {
      410 +                status = NT_STATUS_DELETE_PENDING;
      411 +                goto out;
      412 +        }
      413 +        if (nc->nc_events & FILE_NOTIFY_CHANGE_EV_SUBDIR) {
      414 +                status = NT_STATUS_NOTIFY_ENUM_DIR;
      415 +                goto out;
      416 +        }
      417 +        if (nc->nc_events & FILE_NOTIFY_CHANGE_EV_OVERFLOW) {
      418 +                status = NT_STATUS_NOTIFY_ENUM_DIR;
      419 +                goto out;
      420 +        }
 237  421  
 238      -        return (0);
 239      -}
      422 +        /*
      423 +         * Normal events (FILE_NOTIFY_VALID_MASK)
      424 +         *
      425 +         * At this point there should be some, or else
      426 +         * some sort of bug woke us up for nothing.
      427 +         */
      428 +        if ((nc->nc_events & FILE_NOTIFY_VALID_MASK) == 0) {
      429 +                status = NT_STATUS_INTERNAL_ERROR;
      430 +                goto out;
      431 +        }
 240  432  
 241      -/*
 242      - * smb_notify_file_closed
 243      - *
 244      - * Cancel any change-notify calls on this open file.
 245      - */
 246      -void
 247      -smb_notify_file_closed(struct smb_ofile *of)
 248      -{
 249      -        smb_session_t   *ses;
 250      -        smb_request_t   *sr;
 251      -        smb_slist_t     *list;
      433 +        /*
      434 +         * Many Windows clients call change notify with a
      435 +         * zero-length buffer, expecting all events to be
      436 +         * reported as _ENUM_DIR.  Testing max_bytes here
      437 +         * because ROOM_FOR check below says "yes" if both
      438 +         * max_bytes and the amount we ask for are zero.
      439 +         */
      440 +        if (nc->nc_buffer.max_bytes <= 0) {
      441 +                status = NT_STATUS_NOTIFY_ENUM_DIR;
      442 +                goto out;
      443 +        }
 252  444  
 253      -        SMB_OFILE_VALID(of);
 254      -        ses = of->f_session;
 255      -        SMB_SESSION_VALID(ses);
 256      -        list = &ses->s_req_list;
      445 +        /*
      446 +         * Client gave us a non-zero output buffer, and
      447 +         * there was no overflow event (checked above)
      448 +         * so there should be some event data.
      449 +         */
      450 +        if ((len = nc->nc_buffer.chain_offset) <= 0) {
      451 +                status = NT_STATUS_INTERNAL_ERROR;
      452 +                goto out;
      453 +        }
 257  454  
 258      -        smb_slist_enter(list);
      455 +        /*
      456 +         * If the current SR has a smaller output buffer
      457 +         * then what was setup by some previous notify,
      458 +         * we could have more data than will fit.
      459 +         */
      460 +        if (!MBC_ROOM_FOR(&sr->raw_data, len)) {
      461 +                /* Would overflow caller's buffer. */
      462 +                status = NT_STATUS_NOTIFY_ENUM_DIR;
      463 +                goto out;
      464 +        }
 259  465  
 260      -        sr = smb_slist_head(list);
 261      -        while (sr) {
 262      -                SMB_REQ_VALID(sr);
 263      -                if (sr->sr_state == SMB_REQ_STATE_WAITING_EVENT &&
 264      -                    sr->fid_ofile == of) {
 265      -                        smb_request_cancel(sr);
 266      -                }
 267      -                sr = smb_slist_next(list, sr);
      466 +        /*
      467 +         * Copy the event data to sr->raw_data.  In the copy,
      468 +         * zap the NextEntryOffset in the last entry, and
      469 +         * trim any extra bytes at the tail.
      470 +         */
      471 +        (void) smb_mbc_copy(&sr->raw_data, &nc->nc_buffer, 0, len);
      472 +        (void) smb_mbc_poke(&sr->raw_data, nc->nc_last_off, "l", 0);
      473 +        smb_mbuf_trim(sr->raw_data.chain, len);
      474 +        status = 0;
      475 +
      476 +out:
      477 +        /*
      478 +         * If there are no other SRs waiting on this ofile,
      479 +         * mark all events consumed, except for those that
      480 +         * remain until the ofile is closed.  That means
      481 +         * clear all bits EXCEPT: _EV_CLOSED, _EV_DELETE
      482 +         *
      483 +         * If there are other waiters (rare) all will get
      484 +         * the currently pending events, and then the
      485 +         * the last one out will clear the events.
      486 +         */
      487 +        if (list_is_empty(&nc->nc_waiters)) {
      488 +                nc->nc_buffer.chain_offset = 0;
      489 +                nc->nc_events &= (FILE_NOTIFY_CHANGE_EV_CLOSED |
      490 +                    FILE_NOTIFY_CHANGE_EV_DELETE);
 268  491          }
 269  492  
 270      -        smb_slist_exit(list);
      493 +        return (status);
 271  494  }
 272  495  
      496 +/*
      497 + * Called by common code after a transition from
      498 + * state WAITING_FCN1 to state CANCEL_PENDING.
      499 + */
      500 +static void
      501 +smb_notify_cancel(smb_request_t *sr)
      502 +{
      503 +        ASSERT3U(sr->sr_state, ==, SMB_REQ_STATE_CANCEL_PENDING);
      504 +        smb_notify_dispatch2(sr);
      505 +}
 273  506  
 274  507  /*
 275      - * smb_notify_event
 276      - *
 277      - * Post an event to the watchers on a given node.
 278      - *
 279      - * This makes one exception for RENAME, where we expect a
 280      - * pair of events for the {old,new} directory element names.
 281      - * This only delivers an event for the "new" name.
 282      - *
 283      - * The event delivery mechanism does not implement delivery of
 284      - * multiple events for one "NT Notify" call.  One could do that,
 285      - * but modern clients don't actually use the event data.  They
 286      - * set a max. received data size of zero, which means we discard
 287      - * the data and send the special "lots changed" error instead.
 288      - * Given that, there's not really any point in implementing the
 289      - * delivery of multiple events.  In fact, we don't even need to
 290      - * implement single event delivery, but do so for completeness,
 291      - * for debug convenience, and to be nice to older clients that
 292      - * may actually want some event data instead of the error.
 293      - *
 294      - * Given that we only deliver a single event for an "NT Notify"
 295      - * caller, we want to deliver the "new" name event.  (The "old"
 296      - * name event is less important, even ignored by some clients.)
 297      - * Since we know these are delivered in pairs, we can simply
 298      - * discard the "old" name event, knowing that the "new" name
 299      - * event will be delivered immediately afterwards.
 300      - *
 301      - * So, why do event sources post the "old name" event at all?
 302      - * (1) For debugging, so we see both {old,new} names here.
 303      - * (2) If in the future someone decides to implement the
 304      - * delivery of both {old,new} events, the changes can be
 305      - * mostly isolated to this file.
      508 + * Called after ofile event delivery to take a waiting smb request
      509 + * from state FCN1 to state FCN2.  This may be called many times
      510 + * (as events are delivered) but it must (exactly once) schedule
      511 + * the taskq job to run smb_notify_act3().  Only the event that
      512 + * takes us from state FCN1 to FCN2 schedules the taskq job.
 306  513   */
 307      -void
 308      -smb_notify_event(smb_node_t *node, uint_t action, const char *name)
      514 +static void
      515 +smb_notify_wakeup(smb_request_t *sr)
 309  516  {
 310      -        smb_request_t   *sr;
 311      -        smb_node_fcn_t  *fcn;
      517 +        boolean_t do_disp = B_FALSE;
 312  518  
 313      -        SMB_NODE_VALID(node);
 314      -        fcn = &node->n_fcn;
      519 +        SMB_REQ_VALID(sr);
 315  520  
 316      -        if (action == FILE_ACTION_RENAMED_OLD_NAME)
 317      -                return; /* see above */
      521 +        mutex_enter(&sr->sr_mutex);
      522 +        if (sr->sr_state == SMB_REQ_STATE_WAITING_FCN1) {
      523 +                sr->sr_state = SMB_REQ_STATE_WAITING_FCN2;
      524 +                do_disp = B_TRUE;
      525 +        }
      526 +        mutex_exit(&sr->sr_mutex);
 318  527  
 319      -        mutex_enter(&fcn->fcn_mutex);
 320      -
 321      -        sr = list_head(&fcn->fcn_watchers);
 322      -        while (sr) {
 323      -                smb_notify_sr(sr, action, name);
 324      -                sr = list_next(&fcn->fcn_watchers, sr);
      528 +        if (do_disp) {
      529 +                smb_notify_dispatch2(sr);
 325  530          }
      531 +}
 326  532  
 327      -        mutex_exit(&fcn->fcn_mutex);
      533 +/*
      534 + * smb_notify_dispatch2()
      535 + * Schedule a 2nd taskq call to finish up a change notify request;
      536 + * (smb_notify_act3) either completing it or cancelling it.
      537 + */
      538 +static void
      539 +smb_notify_dispatch2(smb_request_t *sr)
      540 +{
      541 +        void (*tq_func)(void *);
      542 +
      543 +        /*
      544 +         * Both of these call smb_notify_act3(), returning
      545 +         * to version-specific code to send the response.
      546 +         */
      547 +        if (sr->session->dialect >= SMB_VERS_2_BASE)
      548 +                tq_func = smb2_change_notify_finish;
      549 +        else
      550 +                tq_func = smb_nt_transact_notify_finish;
      551 +
      552 +        (void) taskq_dispatch(sr->sr_server->sv_worker_pool,
      553 +            tq_func, sr, TQ_SLEEP);
 328  554  }
 329  555  
      556 +
 330  557  /*
 331  558   * What completion filter (masks) apply to each of the
 332  559   * FILE_ACTION_... events.
 333  560   */
 334  561  static const uint32_t
 335  562  smb_notify_action_mask[] = {
 336  563          0,  /* not used */
 337  564  
 338  565          /* FILE_ACTION_ADDED     */
 339  566          FILE_NOTIFY_CHANGE_NAME |
↓ open down ↓ 24 lines elided ↑ open up ↑
 364  591          FILE_NOTIFY_CHANGE_STREAM_NAME,
 365  592  
 366  593          /* FILE_ACTION_REMOVED_STREAM */
 367  594          FILE_NOTIFY_CHANGE_STREAM_NAME,
 368  595  
 369  596          /* FILE_ACTION_MODIFIED_STREAM */
 370  597          FILE_NOTIFY_CHANGE_STREAM_SIZE |
 371  598          FILE_NOTIFY_CHANGE_STREAM_WRITE,
 372  599  
 373  600          /* FILE_ACTION_SUBDIR_CHANGED */
 374      -        NODE_FLAGS_WATCH_TREE,
      601 +        FILE_NOTIFY_CHANGE_EV_SUBDIR,
 375  602  
 376  603          /* FILE_ACTION_DELETE_PENDING */
 377      -        NODE_FLAGS_WATCH_TREE |
 378      -        FILE_NOTIFY_VALID_MASK,
      604 +        FILE_NOTIFY_CHANGE_EV_DELETE,
      605 +
      606 +        /* FILE_ACTION_HANDLE_CLOSED */
      607 +        FILE_NOTIFY_CHANGE_EV_CLOSED,
 379  608  };
 380  609  static const int smb_notify_action_nelm =
 381  610          sizeof (smb_notify_action_mask) /
 382  611          sizeof (smb_notify_action_mask[0]);
 383  612  
 384  613  /*
 385      - * smb_notify_sr
      614 + * smb_notify_ofile
 386  615   *
 387      - * Post an event to an smb request waiting on some node.
 388      - *
 389      - * Note that node->fcn.mutex is held.  This implies a
 390      - * lock order: node->fcn.mutex, then sr_mutex
      616 + * Post an event to the change notify buffer for this ofile,
      617 + * subject to the mask that selects subscribed event types.
      618 + * If an SR is waiting for events and we've delivered some,
      619 + * wake the SR.
 391  620   */
 392      -static void
 393      -smb_notify_sr(smb_request_t *sr, uint_t action, const char *name)
      621 +void
      622 +smb_notify_ofile(smb_ofile_t *of, uint_t action, const char *name)
 394  623  {
 395      -        smb_notify_change_req_t *ncr;
 396      -        uint32_t        mask;
      624 +        smb_notify_t    *nc;
      625 +        smb_request_t   *sr;
      626 +        uint32_t        filter, events;
 397  627  
 398      -        SMB_REQ_VALID(sr);
 399      -        ncr = &sr->sr_ncr;
      628 +        SMB_OFILE_VALID(of);
 400  629  
      630 +        mutex_enter(&of->f_mutex);
      631 +        nc = &of->f_notify;
      632 +
 401  633          /*
 402      -         * Compute the completion filter mask bits for which
 403      -         * we will signal waiting notify requests.
      634 +         * Compute the filter & event bits for this action,
      635 +         * which determine whether we'll post the event.
      636 +         * Note: always sensitive to: delete, closed.
 404  637           */
      638 +        filter = nc->nc_filter |
      639 +            FILE_NOTIFY_CHANGE_EV_DELETE |
      640 +            FILE_NOTIFY_CHANGE_EV_CLOSED;
 405  641          VERIFY(action < smb_notify_action_nelm);
 406      -        mask = smb_notify_action_mask[action];
      642 +        events = smb_notify_action_mask[action];
      643 +        if ((filter & events) == 0)
      644 +                goto unlock_out;
 407  645  
 408      -        mutex_enter(&sr->sr_mutex);
 409      -        if (sr->sr_state == SMB_REQ_STATE_WAITING_EVENT &&
 410      -            (ncr->nc_flags & mask) != 0) {
 411      -                sr->sr_state = SMB_REQ_STATE_EVENT_OCCURRED;
      646 +        /*
      647 +         * OK, we're going to post this event.
      648 +         */
      649 +        switch (action) {
      650 +        case FILE_ACTION_ADDED:
      651 +        case FILE_ACTION_REMOVED:
      652 +        case FILE_ACTION_MODIFIED:
      653 +        case FILE_ACTION_RENAMED_OLD_NAME:
      654 +        case FILE_ACTION_RENAMED_NEW_NAME:
      655 +        case FILE_ACTION_ADDED_STREAM:
      656 +        case FILE_ACTION_REMOVED_STREAM:
      657 +        case FILE_ACTION_MODIFIED_STREAM:
 412  658                  /*
 413      -                 * Save event data in the sr_ncr field so the
 414      -                 * reply handler can return it.
      659 +                 * Append this event to the buffer.
      660 +                 * Also keep track of events seen.
 415  661                   */
 416      -                ncr->nc_action = action;
 417      -                if (name != NULL)
 418      -                        (void) strlcpy(ncr->nc_fname, name, MAXNAMELEN);
 419      -                cv_signal(&ncr->nc_cv);
      662 +                smb_notify_encode_action(of, action, name);
      663 +                nc->nc_events |= events;
      664 +                break;
      665 +
      666 +        case FILE_ACTION_SUBDIR_CHANGED:
      667 +        case FILE_ACTION_DELETE_PENDING:
      668 +        case FILE_ACTION_HANDLE_CLOSED:
      669 +                /*
      670 +                 * These are "internal" events, and therefore
      671 +                 * are not appended to the response buffer.
      672 +                 * Just record the event flags and wakeup.
      673 +                 */
      674 +                nc->nc_events |= events;
      675 +                break;
      676 +
      677 +        default:
      678 +                ASSERT(0);      /* bogus action */
      679 +                break;
 420  680          }
 421      -        mutex_exit(&sr->sr_mutex);
      681 +
      682 +        sr = list_head(&nc->nc_waiters);
      683 +        while (sr != NULL) {
      684 +                smb_notify_wakeup(sr);
      685 +                sr = list_next(&nc->nc_waiters, sr);
      686 +        }
      687 +
      688 +unlock_out:
      689 +        mutex_exit(&of->f_mutex);
      690 +}
      691 +
      692 +/*
      693 + * Encode a FILE_NOTIFY_INFORMATION struct.
      694 + */
      695 +static void
      696 +smb_notify_encode_action(smb_ofile_t *of,
      697 +    uint32_t action, const char *fname)
      698 +{
      699 +        smb_notify_t *nc = &of->f_notify;
      700 +        mbuf_chain_t *mbc;
      701 +        uint32_t namelen, totlen;
      702 +
      703 +        ASSERT(nc != NULL);
      704 +        ASSERT(FILE_ACTION_ADDED <= action &&
      705 +            action <= FILE_ACTION_MODIFIED_STREAM);
      706 +        ASSERT(fname != NULL);
      707 +        ASSERT(MUTEX_HELD(&of->f_mutex));
      708 +
      709 +        /* Once we've run out of room, stop trying to append. */
      710 +        if ((nc->nc_events & FILE_NOTIFY_CHANGE_EV_OVERFLOW) != 0)
      711 +                return;
      712 +
      713 +        if (fname == NULL)
      714 +                return;
      715 +        namelen = smb_wcequiv_strlen(fname);
      716 +        if (namelen == 0)
      717 +                return;
      718 +
      719 +        /*
      720 +         * Layout is: 3 DWORDS, Unicode string, pad(4).
      721 +         */
      722 +        mbc = &nc->nc_buffer;
      723 +        totlen = (12 + namelen + 3) & ~3;
      724 +        if (MBC_ROOM_FOR(mbc, totlen) == 0) {
      725 +                nc->nc_events |= FILE_NOTIFY_CHANGE_EV_OVERFLOW;
      726 +                return;
      727 +        }
      728 +
      729 +        /*
      730 +         * Keep track of where this entry starts (nc_last_off)
      731 +         * because after we put all entries, we need to zap
      732 +         * the NextEntryOffset field in the last one.
      733 +         */
      734 +        nc->nc_last_off = mbc->chain_offset;
      735 +
      736 +        /*
      737 +         * Encode this entry, then 4-byte alignment padding.
      738 +         *
      739 +         * Note that smb_mbc_encodef with a "U" code puts a
      740 +         * Unicode string with a null termination.  We don't
      741 +         * want a null, but do want alignment padding.  We
      742 +         * get that by encoding with "U.." at the end of the
      743 +         * encoding string, which gets us two bytes for the
      744 +         * Unicode NULL, and two more zeros for the "..".
      745 +         * We then "back up" the chain_offset (finger) so it's
      746 +         * correctly 4-byte aligned.  We will sometimes have
      747 +         * written a couple more bytes than needed, but we'll
      748 +         * just overwrite those with the next entry.  At the
      749 +         * end, we trim the mbuf chain to the correct length.
      750 +         */
      751 +        (void) smb_mbc_encodef(mbc, "lllU..",
      752 +            totlen, /* NextEntryOffset */
      753 +            action, namelen, fname);
      754 +        mbc->chain_offset = nc->nc_last_off + totlen;
 422  755  }
    
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX