1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 
  22 /*
  23  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
  24  * Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
  25  */
  26 
  27 /*
  28  * File Change Notification (FCN)
  29  * Common parts shared by SMB1 & SMB2
  30  */
  31 
  32 /*
  33  * This command notifies the client when the specified directory
  34  * has changed, and optionally returns the names of files and
  35  * directories that changed, and how they changed.  The caller
  36  * specifies a "Completion Filter" to select which kinds of
  37  * changes they want to know about.
  38  *
  39  * When a change that's in the CompletionFilter is made to the directory,
  40  * the command completes.  The names of the files that have changed since
  41  * the last time the command was issued are returned to the client.
  42  * If too many files have changed since the last time the command was
  43  * issued, then zero bytes are returned and an alternate status code
  44  * is returned in the Status field of the response.
  45  *
  46  * The CompletionFilter is a mask created as the sum of any of the
  47  * following flags:
  48  *
  49  * FILE_NOTIFY_CHANGE_FILE_NAME        0x00000001
  50  * FILE_NOTIFY_CHANGE_DIR_NAME         0x00000002
  51  * FILE_NOTIFY_CHANGE_NAME             0x00000003
  52  * FILE_NOTIFY_CHANGE_ATTRIBUTES       0x00000004
  53  * FILE_NOTIFY_CHANGE_SIZE             0x00000008
  54  * FILE_NOTIFY_CHANGE_LAST_WRITE       0x00000010
  55  * FILE_NOTIFY_CHANGE_LAST_ACCESS      0x00000020
  56  * FILE_NOTIFY_CHANGE_CREATION         0x00000040
  57  * FILE_NOTIFY_CHANGE_EA               0x00000080
  58  * FILE_NOTIFY_CHANGE_SECURITY         0x00000100
  59  * FILE_NOTIFY_CHANGE_STREAM_NAME      0x00000200
  60  * FILE_NOTIFY_CHANGE_STREAM_SIZE      0x00000400
  61  * FILE_NOTIFY_CHANGE_STREAM_WRITE     0x00000800
  62  *
  63  *
  64  * The response contains FILE_NOTIFY_INFORMATION structures, as defined
  65  * below.  The NextEntryOffset field of the structure specifies the offset,
  66  * in bytes, from the start of the current entry to the next entry in the
  67  * list.  If this is the last entry in the list, this field is zero.  Each
  68  * entry in the list must be longword aligned, so NextEntryOffset must be a
  69  * multiple of four.
  70  *
  71  * typedef struct {
  72  *     ULONG NextEntryOffset;
  73  *     ULONG Action;
  74  *     ULONG FileNameLength;
  75  *     WCHAR FileName[1];
  76  * } FILE_NOTIFY_INFORMATION;
  77  *
  78  * Where Action describes what happened to the file named FileName:
  79  *
  80  * FILE_ACTION_ADDED            0x00000001
  81  * FILE_ACTION_REMOVED          0x00000002
  82  * FILE_ACTION_MODIFIED         0x00000003
  83  * FILE_ACTION_RENAMED_OLD_NAME 0x00000004
  84  * FILE_ACTION_RENAMED_NEW_NAME 0x00000005
  85  * FILE_ACTION_ADDED_STREAM     0x00000006
  86  * FILE_ACTION_REMOVED_STREAM   0x00000007
  87  * FILE_ACTION_MODIFIED_STREAM  0x00000008
  88  */
  89 
  90 #include <smbsrv/smb_kproto.h>
  91 #include <sys/sdt.h>
  92 
  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 *);
  96 
  97 uint32_t
  98 smb_notify_common(smb_request_t *sr, mbuf_chain_t *mbc,
  99         uint32_t CompletionFilter)
 100 {
 101         smb_notify_change_req_t *nc;
 102         smb_node_t      *node;
 103         uint32_t        status;
 104 
 105         if (sr->fid_ofile == NULL)
 106                 return (NT_STATUS_INVALID_HANDLE);
 107 
 108         node = sr->fid_ofile->f_node;
 109         if (node == NULL || !smb_node_is_dir(node)) {
 110                 /*
 111                  * Notify change is only valid on directories.
 112                  */
 113                 return (NT_STATUS_INVALID_PARAMETER);
 114         }
 115 
 116         /*
 117          * Prepare to receive event data.
 118          */
 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 
 125         /*
 126          * Subscribe to events on this node.
 127          */
 128         smb_node_fcn_subscribe(node, sr);
 129 
 130         /*
 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.
 136          */
 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);
 146 
 147         /*
 148          * Unsubscribe from events on this node.
 149          */
 150         smb_node_fcn_unsubscribe(node, sr);
 151 
 152         /*
 153          * Why did we wake up?
 154          */
 155         switch (sr->sr_state) {
 156         case SMB_REQ_STATE_ACTIVE:
 157                 break;
 158         case SMB_REQ_STATE_CANCELED:
 159                 status = NT_STATUS_CANCELLED;
 160                 goto out;
 161         default:
 162                 status = NT_STATUS_INTERNAL_ERROR;
 163                 goto out;
 164         }
 165 
 166         /*
 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".
 174          */
 175         switch (nc->nc_action) {
 176 
 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:
 185                 /*
 186                  * Build the reply
 187                  */
 188                 status = smb_notify_encode_action(sr, mbc,
 189                     nc->nc_action, nc->nc_fname);
 190                 break;
 191 
 192         case FILE_ACTION_SUBDIR_CHANGED:
 193                 status = NT_STATUS_NOTIFY_ENUM_DIR;
 194                 break;
 195 
 196         case FILE_ACTION_DELETE_PENDING:
 197                 status = NT_STATUS_DELETE_PENDING;
 198                 break;
 199 
 200         default:
 201                 ASSERT(0);
 202                 status = NT_STATUS_INTERNAL_ERROR;
 203                 break;
 204         }
 205 
 206 out:
 207         kmem_free(nc->nc_fname, MAXNAMELEN);
 208         nc->nc_fname = NULL;
 209         return (status);
 210 }
 211 
 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 static uint32_t
 219 smb_notify_encode_action(struct smb_request *sr, mbuf_chain_t *mbc,
 220         uint32_t action, char *fname)
 221 {
 222         uint32_t namelen;
 223 
 224         ASSERT(FILE_ACTION_ADDED <= action &&
 225             action <= FILE_ACTION_MODIFIED_STREAM);
 226 
 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);
 232 
 233         if (smb_mbc_encodef(mbc, "%lllU", sr,
 234             0, /* NextEntryOffset */
 235             action, namelen, fname))
 236                 return (NT_STATUS_NOTIFY_ENUM_DIR);
 237 
 238         return (0);
 239 }
 240 
 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;
 252 
 253         SMB_OFILE_VALID(of);
 254         ses = of->f_session;
 255         SMB_SESSION_VALID(ses);
 256         list = &ses->s_req_list;
 257 
 258         smb_slist_enter(list);
 259 
 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);
 268         }
 269 
 270         smb_slist_exit(list);
 271 }
 272 
 273 
 274 /*
 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.
 306  */
 307 void
 308 smb_notify_event(smb_node_t *node, uint_t action, const char *name)
 309 {
 310         smb_request_t   *sr;
 311         smb_node_fcn_t  *fcn;
 312 
 313         SMB_NODE_VALID(node);
 314         fcn = &node->n_fcn;
 315 
 316         if (action == FILE_ACTION_RENAMED_OLD_NAME)
 317                 return; /* see above */
 318 
 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);
 325         }
 326 
 327         mutex_exit(&fcn->fcn_mutex);
 328 }
 329 
 330 /*
 331  * What completion filter (masks) apply to each of the
 332  * FILE_ACTION_... events.
 333  */
 334 static const uint32_t
 335 smb_notify_action_mask[] = {
 336         0,  /* not used */
 337 
 338         /* FILE_ACTION_ADDED     */
 339         FILE_NOTIFY_CHANGE_NAME |
 340         FILE_NOTIFY_CHANGE_LAST_WRITE,
 341 
 342         /* FILE_ACTION_REMOVED   */
 343         FILE_NOTIFY_CHANGE_NAME |
 344         FILE_NOTIFY_CHANGE_LAST_WRITE,
 345 
 346         /* FILE_ACTION_MODIFIED  */
 347         FILE_NOTIFY_CHANGE_ATTRIBUTES |
 348         FILE_NOTIFY_CHANGE_SIZE |
 349         FILE_NOTIFY_CHANGE_LAST_WRITE |
 350         FILE_NOTIFY_CHANGE_LAST_ACCESS |
 351         FILE_NOTIFY_CHANGE_CREATION |
 352         FILE_NOTIFY_CHANGE_EA |
 353         FILE_NOTIFY_CHANGE_SECURITY,
 354 
 355         /* FILE_ACTION_RENAMED_OLD_NAME */
 356         FILE_NOTIFY_CHANGE_NAME |
 357         FILE_NOTIFY_CHANGE_LAST_WRITE,
 358 
 359         /* FILE_ACTION_RENAMED_NEW_NAME */
 360         FILE_NOTIFY_CHANGE_NAME |
 361         FILE_NOTIFY_CHANGE_LAST_WRITE,
 362 
 363         /* FILE_ACTION_ADDED_STREAM */
 364         FILE_NOTIFY_CHANGE_STREAM_NAME,
 365 
 366         /* FILE_ACTION_REMOVED_STREAM */
 367         FILE_NOTIFY_CHANGE_STREAM_NAME,
 368 
 369         /* FILE_ACTION_MODIFIED_STREAM */
 370         FILE_NOTIFY_CHANGE_STREAM_SIZE |
 371         FILE_NOTIFY_CHANGE_STREAM_WRITE,
 372 
 373         /* FILE_ACTION_SUBDIR_CHANGED */
 374         NODE_FLAGS_WATCH_TREE,
 375 
 376         /* FILE_ACTION_DELETE_PENDING */
 377         NODE_FLAGS_WATCH_TREE |
 378         FILE_NOTIFY_VALID_MASK,
 379 };
 380 static const int smb_notify_action_nelm =
 381         sizeof (smb_notify_action_mask) /
 382         sizeof (smb_notify_action_mask[0]);
 383 
 384 /*
 385  * smb_notify_sr
 386  *
 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
 391  */
 392 static void
 393 smb_notify_sr(smb_request_t *sr, uint_t action, const char *name)
 394 {
 395         smb_notify_change_req_t *ncr;
 396         uint32_t        mask;
 397 
 398         SMB_REQ_VALID(sr);
 399         ncr = &sr->sr_ncr;
 400 
 401         /*
 402          * Compute the completion filter mask bits for which
 403          * we will signal waiting notify requests.
 404          */
 405         VERIFY(action < smb_notify_action_nelm);
 406         mask = smb_notify_action_mask[action];
 407 
 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;
 412                 /*
 413                  * Save event data in the sr_ncr field so the
 414                  * reply handler can return it.
 415                  */
 416                 ncr->nc_action = action;
 417                 if (name != NULL)
 418                         (void) strlcpy(ncr->nc_fname, name, MAXNAMELEN);
 419                 cv_signal(&ncr->nc_cv);
 420         }
 421         mutex_exit(&sr->sr_mutex);
 422 }