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 2012 Nexenta Systems, Inc. All rights reserved. 25 */ 26 27 /* 28 * File Change Notification (FCN) 29 */ 30 31 /* 32 * SMB: nt_transact_notify_change 33 * 34 * Client Setup Words Description 35 * ================================== ================================= 36 * 37 * ULONG CompletionFilter; Specifies operation to monitor 38 * USHORT Fid; Fid of directory to monitor 39 * BOOLEAN WatchTree; TRUE = watch all subdirectories too 40 * UCHAR Reserved; MBZ 41 * 42 * This command notifies the client when the directory specified by Fid is 43 * modified. It also returns the name(s) of the file(s) that changed. The 44 * command completes once the directory has been modified based on the 45 * supplied CompletionFilter. The command is a "single shot" and therefore 46 * needs to be reissued to watch for more directory changes. 47 * 48 * A directory file must be opened before this command may be used. Once 49 * the directory is open, this command may be used to begin watching files 50 * and subdirectories in the specified directory for changes. The first 51 * time the command is issued, the MaxParameterCount field in the transact 52 * header determines the size of the buffer that will be used at the server 53 * to buffer directory change information between issuances of the notify 54 * change commands. 55 * 56 * When a change that is in the CompletionFilter is made to the directory, 57 * the command completes. The names of the files that have changed since 58 * the last time the command was issued are returned to the client. The 59 * ParameterCount field of the response indicates the number of bytes that 60 * are being returned. If too many files have changed since the last time 61 * the command was issued, then zero bytes are returned and an alternate 62 * status code is returned in the Status field of the response. 63 * 64 * The CompletionFilter is a mask created as the sum of any of the 65 * following flags: 66 * 67 * FILE_NOTIFY_CHANGE_FILE_NAME 0x00000001 68 * FILE_NOTIFY_CHANGE_DIR_NAME 0x00000002 69 * FILE_NOTIFY_CHANGE_NAME 0x00000003 70 * FILE_NOTIFY_CHANGE_ATTRIBUTES 0x00000004 71 * FILE_NOTIFY_CHANGE_SIZE 0x00000008 72 * FILE_NOTIFY_CHANGE_LAST_WRITE 0x00000010 73 * FILE_NOTIFY_CHANGE_LAST_ACCESS 0x00000020 74 * FILE_NOTIFY_CHANGE_CREATION 0x00000040 75 * FILE_NOTIFY_CHANGE_EA 0x00000080 76 * FILE_NOTIFY_CHANGE_SECURITY 0x00000100 77 * FILE_NOTIFY_CHANGE_STREAM_NAME 0x00000200 78 * FILE_NOTIFY_CHANGE_STREAM_SIZE 0x00000400 79 * FILE_NOTIFY_CHANGE_STREAM_WRITE 0x00000800 80 * 81 * Server Response Description 82 * ================================== ================================ 83 * ParameterCount # of bytes of change data 84 * Parameters[ ParameterCount ] FILE_NOTIFY_INFORMATION 85 * structures 86 * 87 * The response contains FILE_NOTIFY_INFORMATION structures, as defined 88 * below. The NextEntryOffset field of the structure specifies the offset, 89 * in bytes, from the start of the current entry to the next entry in the 90 * list. If this is the last entry in the list, this field is zero. Each 91 * entry in the list must be longword aligned, so NextEntryOffset must be a 92 * multiple of four. 93 * 94 * typedef struct { 95 * ULONG NextEntryOffset; 96 * ULONG Action; 97 * ULONG FileNameLength; 98 * WCHAR FileName[1]; 99 * } FILE_NOTIFY_INFORMATION; 100 * 101 * Where Action describes what happened to the file named FileName: 102 * 103 * FILE_ACTION_ADDED 0x00000001 104 * FILE_ACTION_REMOVED 0x00000002 105 * FILE_ACTION_MODIFIED 0x00000003 106 * FILE_ACTION_RENAMED_OLD_NAME 0x00000004 107 * FILE_ACTION_RENAMED_NEW_NAME 0x00000005 108 * FILE_ACTION_ADDED_STREAM 0x00000006 109 * FILE_ACTION_REMOVED_STREAM 0x00000007 110 * FILE_ACTION_MODIFIED_STREAM 0x00000008 111 */ 112 113 #include <smbsrv/smb_kproto.h> 114 #include <sys/sdt.h> 115 116 /* 117 * We add this flag to the CompletionFilter (see above) when the 118 * client sets WatchTree. Must not overlap FILE_NOTIFY_VALID_MASK. 119 */ 120 #define NODE_FLAGS_WATCH_TREE 0x10000000 121 #if (NODE_FLAGS_WATCH_TREE & FILE_NOTIFY_VALID_MASK) 122 #error "NODE_FLAGS_WATCH_TREE" 123 #endif 124 125 static void smb_notify_sr(smb_request_t *, uint_t, const char *); 126 127 static int smb_notify_encode_action(struct smb_request *, struct smb_xa *, 128 uint32_t, char *); 129 130 /* 131 * smb_nt_transact_notify_change 132 * 133 * Handle and SMB NT transact NOTIFY CHANGE request. 134 * Basically, wait until "something has changed", and either 135 * return information about what changed, or return a special 136 * error telling the client "many things changed". 137 * 138 * The implementation uses a per-node list of waiting notify 139 * requests like this one, each with a blocked worker thead. 140 * Later, FEM and/or smbsrv events wake these threads, which 141 * then send the reply to the client. 142 */ 143 smb_sdrc_t 144 smb_nt_transact_notify_change(smb_request_t *sr, struct smb_xa *xa) 145 { 146 uint32_t CompletionFilter; 147 unsigned char WatchTree; 148 smb_error_t err; 149 int rc; 150 smb_node_t *node; 151 152 if (smb_mbc_decodef(&xa->req_setup_mb, "lwb", 153 &CompletionFilter, &sr->smb_fid, &WatchTree) != 0) { 154 smbsr_error(sr, NT_STATUS_INVALID_PARAMETER, 0, 0); 155 return (SDRC_ERROR); 156 } 157 CompletionFilter &= FILE_NOTIFY_VALID_MASK; 158 if (WatchTree) 159 CompletionFilter |= NODE_FLAGS_WATCH_TREE; 160 161 smbsr_lookup_file(sr); 162 if (sr->fid_ofile == NULL) { 163 smbsr_error(sr, NT_STATUS_INVALID_HANDLE, ERRDOS, ERRbadfid); 164 return (SDRC_ERROR); 165 } 166 167 node = sr->fid_ofile->f_node; 168 if (node == NULL || !smb_node_is_dir(node)) { 169 /* 170 * Notify change requests are only valid on directories. 171 */ 172 smbsr_error(sr, NT_STATUS_NOT_A_DIRECTORY, 0, 0); 173 return (SDRC_ERROR); 174 } 175 176 /* 177 * Prepare to receive event data. 178 */ 179 sr->sr_ncr.nc_flags = CompletionFilter; 180 ASSERT(sr->sr_ncr.nc_action == 0); 181 ASSERT(sr->sr_ncr.nc_fname == NULL); 182 sr->sr_ncr.nc_fname = kmem_zalloc(MAXNAMELEN, KM_SLEEP); 183 184 /* 185 * Subscribe to events on this node. 186 */ 187 smb_node_fcn_subscribe(node, sr); 188 189 /* 190 * Wait for subscribed events to arrive. 191 * Expect SMB_REQ_STATE_EVENT_OCCURRED 192 * or SMB_REQ_STATE_CANCELED when signaled. 193 * Note it's possible (though rare) to already 194 * have SMB_REQ_STATE_CANCELED here. 195 */ 196 mutex_enter(&sr->sr_mutex); 197 if (sr->sr_state == SMB_REQ_STATE_ACTIVE) 198 sr->sr_state = SMB_REQ_STATE_WAITING_EVENT; 199 while (sr->sr_state == SMB_REQ_STATE_WAITING_EVENT) { 200 cv_wait(&sr->sr_ncr.nc_cv, &sr->sr_mutex); 201 } 202 if (sr->sr_state == SMB_REQ_STATE_EVENT_OCCURRED) 203 sr->sr_state = SMB_REQ_STATE_ACTIVE; 204 mutex_exit(&sr->sr_mutex); 205 206 /* 207 * Unsubscribe from events on this node. 208 */ 209 smb_node_fcn_unsubscribe(node, sr); 210 211 /* 212 * Build the reply 213 */ 214 215 switch (sr->sr_state) { 216 217 case SMB_REQ_STATE_ACTIVE: 218 /* 219 * If we have event data, marshall it now, else just 220 * say "many things changed". Note that when we are 221 * woken by a WatchTree event (action == 0) then we 222 * don't have true event details, and only know the 223 * directory under which something changed. In that 224 * case we just say "many things changed". 225 */ 226 if (sr->sr_ncr.nc_action != 0 && 0 == 227 smb_notify_encode_action(sr, xa, 228 sr->sr_ncr.nc_action, sr->sr_ncr.nc_fname)) { 229 rc = SDRC_SUCCESS; 230 break; 231 } 232 /* 233 * This error says "many things changed". 234 */ 235 err.status = NT_STATUS_NOTIFY_ENUM_DIR; 236 err.errcls = ERRDOS; 237 err.errcode = ERROR_NOTIFY_ENUM_DIR; 238 smbsr_set_error(sr, &err); 239 rc = SDRC_ERROR; 240 break; 241 242 case SMB_REQ_STATE_CANCELED: 243 err.status = NT_STATUS_CANCELLED; 244 err.errcls = ERRDOS; 245 err.errcode = ERROR_OPERATION_ABORTED; 246 smbsr_set_error(sr, &err); 247 rc = SDRC_ERROR; 248 break; 249 250 default: 251 ASSERT(0); 252 err.status = NT_STATUS_INTERNAL_ERROR; 253 err.errcls = ERRDOS; 254 err.errcode = ERROR_INTERNAL_ERROR; 255 smbsr_set_error(sr, &err); 256 rc = SDRC_ERROR; 257 break; 258 } 259 260 if (sr->sr_ncr.nc_fname != NULL) { 261 kmem_free(sr->sr_ncr.nc_fname, MAXNAMELEN); 262 sr->sr_ncr.nc_fname = NULL; 263 } 264 265 return (rc); 266 } 267 268 /* 269 * Encode a FILE_NOTIFY_INFORMATION struct. 270 * 271 * We only ever put one of these in a response, so this 272 * does not bother handling appending additional ones. 273 */ 274 static int 275 smb_notify_encode_action(struct smb_request *sr, struct smb_xa *xa, 276 uint32_t action, char *fname) 277 { 278 uint32_t namelen; 279 int rc; 280 281 if (action < FILE_ACTION_ADDED || 282 action > FILE_ACTION_MODIFIED_STREAM) 283 return (-1); 284 285 namelen = smb_ascii_or_unicode_strlen(sr, fname); 286 if (namelen == 0) 287 return (-1); 288 289 rc = smb_mbc_encodef(&xa->rep_data_mb, "%lllu", sr, 290 0, /* NextEntryOffset */ 291 action, namelen, fname); 292 return (rc); 293 } 294 295 /* 296 * smb_notify_file_closed 297 * 298 * Cancel any change-notify calls on this open file. 299 */ 300 void 301 smb_notify_file_closed(struct smb_ofile *of) 302 { 303 smb_session_t *ses; 304 smb_request_t *sr; 305 smb_slist_t *list; 306 307 SMB_OFILE_VALID(of); 308 ses = of->f_session; 309 SMB_SESSION_VALID(ses); 310 list = &ses->s_req_list; 311 312 smb_slist_enter(list); 313 314 sr = smb_slist_head(list); 315 while (sr) { 316 SMB_REQ_VALID(sr); 317 if (sr->sr_state == SMB_REQ_STATE_WAITING_EVENT && 318 sr->fid_ofile == of) { 319 smb_request_cancel(sr); 320 } 321 sr = smb_slist_next(list, sr); 322 } 323 324 smb_slist_exit(list); 325 } 326 327 328 /* 329 * smb_notify_event 330 * 331 * Post an event to the watchers on a given node. 332 * 333 * This makes one exception for RENAME, where we expect a 334 * pair of events for the {old,new} directory element names. 335 * This only delivers an event for the "new" name. 336 * 337 * The event delivery mechanism does not implement delivery of 338 * multiple events for one "NT Notify" call. One could do that, 339 * but modern clients don't actually use the event data. They 340 * set a max. received data size of zero, which means we discard 341 * the data and send the special "lots changed" error instead. 342 * Given that, there's not really any point in implementing the 343 * delivery of multiple events. In fact, we don't even need to 344 * implement single event delivery, but do so for completeness, 345 * for debug convenience, and to be nice to older clients that 346 * may actually want some event data instead of the error. 347 * 348 * Given that we only deliver a single event for an "NT Notify" 349 * caller, we want to deliver the "new" name event. (The "old" 350 * name event is less important, even ignored by some clients.) 351 * Since we know these are delivered in pairs, we can simply 352 * discard the "old" name event, knowing that the "new" name 353 * event will be delivered immediately afterwards. 354 * 355 * So, why do event sources post the "old name" event at all? 356 * (1) For debugging, so we see both {old,new} names here. 357 * (2) If in the future someone decides to implement the 358 * delivery of both {old,new} events, the changes can be 359 * mostly isolated to this file. 360 */ 361 void 362 smb_notify_event(smb_node_t *node, uint_t action, const char *name) 363 { 364 smb_request_t *sr; 365 smb_node_fcn_t *fcn; 366 367 SMB_NODE_VALID(node); 368 fcn = &node->n_fcn; 369 370 if (action == FILE_ACTION_RENAMED_OLD_NAME) 371 return; /* see above */ 372 373 mutex_enter(&fcn->fcn_mutex); 374 375 sr = list_head(&fcn->fcn_watchers); 376 while (sr) { 377 smb_notify_sr(sr, action, name); 378 sr = list_next(&fcn->fcn_watchers, sr); 379 } 380 381 mutex_exit(&fcn->fcn_mutex); 382 } 383 384 /* 385 * What completion filter (masks) apply to each of the 386 * FILE_ACTION_... events. 387 */ 388 static const uint32_t 389 smb_notify_action_mask[] = { 390 /* 0: Special, used by smb_node_notify_parents() */ 391 NODE_FLAGS_WATCH_TREE, 392 393 /* FILE_ACTION_ADDED */ 394 FILE_NOTIFY_CHANGE_NAME, 395 396 /* FILE_ACTION_REMOVED */ 397 FILE_NOTIFY_CHANGE_NAME, 398 399 /* FILE_ACTION_MODIFIED */ 400 FILE_NOTIFY_CHANGE_ATTRIBUTES | 401 FILE_NOTIFY_CHANGE_SIZE | 402 FILE_NOTIFY_CHANGE_LAST_WRITE | 403 FILE_NOTIFY_CHANGE_LAST_ACCESS | 404 FILE_NOTIFY_CHANGE_CREATION | 405 FILE_NOTIFY_CHANGE_EA | 406 FILE_NOTIFY_CHANGE_SECURITY, 407 408 /* FILE_ACTION_RENAMED_OLD_NAME */ 409 FILE_NOTIFY_CHANGE_NAME, 410 411 /* FILE_ACTION_RENAMED_NEW_NAME */ 412 FILE_NOTIFY_CHANGE_NAME, 413 414 /* FILE_ACTION_ADDED_STREAM */ 415 FILE_NOTIFY_CHANGE_STREAM_NAME, 416 417 /* FILE_ACTION_REMOVED_STREAM */ 418 FILE_NOTIFY_CHANGE_STREAM_NAME, 419 420 /* FILE_ACTION_MODIFIED_STREAM */ 421 FILE_NOTIFY_CHANGE_STREAM_SIZE | 422 FILE_NOTIFY_CHANGE_STREAM_WRITE, 423 }; 424 static const int smb_notify_action_nelm = 425 sizeof (smb_notify_action_mask) / 426 sizeof (smb_notify_action_mask[0]); 427 428 /* 429 * smb_notify_sr 430 * 431 * Post an event to an smb request waiting on some node. 432 * 433 * Note that node->fcn.mutex is held. This implies a 434 * lock order: node->fcn.mutex, then sr_mutex 435 */ 436 static void 437 smb_notify_sr(smb_request_t *sr, uint_t action, const char *name) 438 { 439 smb_notify_change_req_t *ncr; 440 uint32_t mask; 441 442 SMB_REQ_VALID(sr); 443 ncr = &sr->sr_ncr; 444 445 /* 446 * Compute the completion filter mask bits for which 447 * we will signal waiting notify requests. 448 */ 449 if (action >= smb_notify_action_nelm) { 450 ASSERT(0); 451 return; 452 } 453 mask = smb_notify_action_mask[action]; 454 455 mutex_enter(&sr->sr_mutex); 456 if (sr->sr_state == SMB_REQ_STATE_WAITING_EVENT && 457 (ncr->nc_flags & mask) != 0) { 458 sr->sr_state = SMB_REQ_STATE_EVENT_OCCURRED; 459 /* 460 * Save event data in the sr_ncr field so the 461 * reply handler can return it. 462 */ 463 ncr->nc_action = action; 464 if (name != NULL) 465 (void) strlcpy(ncr->nc_fname, name, MAXNAMELEN); 466 cv_signal(&ncr->nc_cv); 467 } 468 mutex_exit(&sr->sr_mutex); 469 } 470