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 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 /* 27 * File Change Notification (FCN) 28 */ 29 30 /* 31 * SMB: nt_transact_notify_change 32 * 33 * Client Setup Words Description 34 * ================================== ================================= 35 * 36 * ULONG CompletionFilter; Specifies operation to monitor 37 * USHORT Fid; Fid of directory to monitor 38 * BOOLEAN WatchTree; TRUE = watch all subdirectories too 39 * UCHAR Reserved; MBZ 40 * 41 * This command notifies the client when the directory specified by Fid is 42 * modified. It also returns the name(s) of the file(s) that changed. The 43 * command completes once the directory has been modified based on the 44 * supplied CompletionFilter. The command is a "single shot" and therefore 45 * needs to be reissued to watch for more directory changes. 46 * 47 * A directory file must be opened before this command may be used. Once 48 * the directory is open, this command may be used to begin watching files 49 * and subdirectories in the specified directory for changes. The first 50 * time the command is issued, the MaxParameterCount field in the transact 51 * header determines the size of the buffer that will be used at the server 52 * to buffer directory change information between issuances of the notify 53 * change commands. 54 * 55 * When a change that is in the CompletionFilter is made to the directory, 56 * the command completes. The names of the files that have changed since 57 * the last time the command was issued are returned to the client. The 58 * ParameterCount field of the response indicates the number of bytes that 59 * are being returned. If too many files have changed since the last time 60 * the command was issued, then zero bytes are returned and an alternate 61 * status code is returned in the Status field of the response. 62 * 63 * The CompletionFilter is a mask created as the sum of any of the 64 * following flags: 65 * 66 * FILE_NOTIFY_CHANGE_FILE_NAME 0x00000001 67 * FILE_NOTIFY_CHANGE_DIR_NAME 0x00000002 68 * FILE_NOTIFY_CHANGE_NAME 0x00000003 69 * FILE_NOTIFY_CHANGE_ATTRIBUTES 0x00000004 70 * FILE_NOTIFY_CHANGE_SIZE 0x00000008 71 * FILE_NOTIFY_CHANGE_LAST_WRITE 0x00000010 72 * FILE_NOTIFY_CHANGE_LAST_ACCESS 0x00000020 73 * FILE_NOTIFY_CHANGE_CREATION 0x00000040 74 * FILE_NOTIFY_CHANGE_EA 0x00000080 75 * FILE_NOTIFY_CHANGE_SECURITY 0x00000100 76 * FILE_NOTIFY_CHANGE_STREAM_NAME 0x00000200 77 * FILE_NOTIFY_CHANGE_STREAM_SIZE 0x00000400 78 * FILE_NOTIFY_CHANGE_STREAM_WRITE 0x00000800 79 * 80 * Server Response Description 81 * ================================== ================================ 82 * ParameterCount # of bytes of change data 83 * Parameters[ ParameterCount ] FILE_NOTIFY_INFORMATION 84 * structures 85 * 86 * The response contains FILE_NOTIFY_INFORMATION structures, as defined 87 * below. The NextEntryOffset field of the structure specifies the offset, 88 * in bytes, from the start of the current entry to the next entry in the 89 * list. If this is the last entry in the list, this field is zero. Each 90 * entry in the list must be longword aligned, so NextEntryOffset must be a 91 * multiple of four. 92 * 93 * typedef struct { 94 * ULONG NextEntryOffset; 95 * ULONG Action; 96 * ULONG FileNameLength; 97 * WCHAR FileName[1]; 98 * } FILE_NOTIFY_INFORMATION; 99 * 100 * Where Action describes what happened to the file named FileName: 101 * 102 * FILE_ACTION_ADDED 0x00000001 103 * FILE_ACTION_REMOVED 0x00000002 104 * FILE_ACTION_MODIFIED 0x00000003 105 * FILE_ACTION_RENAMED_OLD_NAME 0x00000004 106 * FILE_ACTION_RENAMED_NEW_NAME 0x00000005 107 * FILE_ACTION_ADDED_STREAM 0x00000006 108 * FILE_ACTION_REMOVED_STREAM 0x00000007 109 * FILE_ACTION_MODIFIED_STREAM 0x00000008 110 */ 111 112 #include <smbsrv/smb_kproto.h> 113 #include <sys/sdt.h> 114 115 static void smb_notify_change_daemon(smb_thread_t *, void *); 116 static boolean_t smb_notify_change_required(smb_request_t *, smb_node_t *); 117 118 static boolean_t smb_notify_initialized = B_FALSE; 119 static smb_slist_t smb_ncr_list; 120 static smb_slist_t smb_nce_list; 121 static smb_thread_t smb_thread_notify_daemon; 122 123 /* 124 * smb_notify_init 125 * 126 * This function is not multi-thread safe. The caller must make sure only one 127 * thread makes the call. 128 */ 129 int 130 smb_notify_init(void) 131 { 132 int rc; 133 134 if (smb_notify_initialized) 135 return (0); 136 137 smb_slist_constructor(&smb_ncr_list, sizeof (smb_request_t), 138 offsetof(smb_request_t, sr_ncr.nc_lnd)); 139 140 smb_slist_constructor(&smb_nce_list, sizeof (smb_request_t), 141 offsetof(smb_request_t, sr_ncr.nc_lnd)); 142 143 smb_thread_init(&smb_thread_notify_daemon, 144 "smb_notify_change_daemon", smb_notify_change_daemon, NULL, 145 NULL, NULL); 146 147 rc = smb_thread_start(&smb_thread_notify_daemon); 148 if (rc) { 149 smb_thread_destroy(&smb_thread_notify_daemon); 150 smb_slist_destructor(&smb_ncr_list); 151 smb_slist_destructor(&smb_nce_list); 152 return (rc); 153 } 154 155 smb_notify_initialized = B_TRUE; 156 157 return (0); 158 } 159 160 /* 161 * smb_notify_fini 162 * 163 * This function is not multi-thread safe. The caller must make sure only one 164 * thread makes the call. 165 */ 166 void 167 smb_notify_fini(void) 168 { 169 if (!smb_notify_initialized) 170 return; 171 172 smb_thread_stop(&smb_thread_notify_daemon); 173 smb_thread_destroy(&smb_thread_notify_daemon); 174 smb_slist_destructor(&smb_ncr_list); 175 smb_slist_destructor(&smb_nce_list); 176 smb_notify_initialized = B_FALSE; 177 } 178 179 /* 180 * smb_nt_transact_notify_change 181 * 182 * This function is responsible for processing NOTIFY CHANGE requests. 183 * Requests are stored in a global queue. This queue is processed when 184 * a monitored directory is changed or client cancels one of its already 185 * sent requests. 186 */ 187 smb_sdrc_t 188 smb_nt_transact_notify_change(struct smb_request *sr, struct smb_xa *xa) 189 { 190 uint32_t CompletionFilter; 191 unsigned char WatchTree; 192 smb_node_t *node; 193 194 if (smb_mbc_decodef(&xa->req_setup_mb, "lwb", 195 &CompletionFilter, &sr->smb_fid, &WatchTree) != 0) 196 return (SDRC_NOT_IMPLEMENTED); 197 198 smbsr_lookup_file(sr); 199 if (sr->fid_ofile == NULL) { 200 smbsr_error(sr, NT_STATUS_INVALID_HANDLE, ERRDOS, ERRbadfid); 201 return (SDRC_ERROR); 202 } 203 204 node = sr->fid_ofile->f_node; 205 206 if (!smb_node_is_dir(node)) { 207 /* 208 * Notify change requests are only valid on directories. 209 */ 210 smbsr_error(sr, NT_STATUS_NOT_A_DIRECTORY, 0, 0); 211 return (SDRC_ERROR); 212 } 213 214 mutex_enter(&sr->sr_mutex); 215 switch (sr->sr_state) { 216 case SMB_REQ_STATE_ACTIVE: 217 node->waiting_event++; 218 node->flags |= NODE_FLAGS_NOTIFY_CHANGE; 219 if ((node->flags & NODE_FLAGS_CHANGED) == 0) { 220 sr->sr_ncr.nc_node = node; 221 sr->sr_ncr.nc_flags = CompletionFilter; 222 if (WatchTree) 223 sr->sr_ncr.nc_flags |= NODE_FLAGS_WATCH_TREE; 224 225 sr->sr_keep = B_TRUE; 226 sr->sr_state = SMB_REQ_STATE_WAITING_EVENT; 227 228 smb_slist_insert_tail(&smb_ncr_list, sr); 229 230 /* 231 * Monitor events system-wide. 232 * 233 * XXX: smb_node_ref() and smb_node_release() 234 * take &node->n_lock. May need alternate forms 235 * of these routines if node->n_lock is taken 236 * around calls to smb_fem_fcn_install() and 237 * smb_fem_fcn_uninstall(). 238 */ 239 240 smb_fem_fcn_install(node); 241 242 mutex_exit(&sr->sr_mutex); 243 return (SDRC_SR_KEPT); 244 } else { 245 /* node already changed, reply immediately */ 246 if (--node->waiting_event == 0) 247 node->flags &= 248 ~(NODE_FLAGS_NOTIFY_CHANGE | 249 NODE_FLAGS_CHANGED); 250 mutex_exit(&sr->sr_mutex); 251 return (SDRC_SUCCESS); 252 } 253 254 case SMB_REQ_STATE_CANCELED: 255 mutex_exit(&sr->sr_mutex); 256 smbsr_error(sr, NT_STATUS_CANCELLED, 0, 0); 257 return (SDRC_ERROR); 258 259 default: 260 ASSERT(0); 261 mutex_exit(&sr->sr_mutex); 262 return (SDRC_SUCCESS); 263 } 264 } 265 266 /* 267 * smb_reply_notify_change_request 268 * 269 * This function sends appropriate response to an already queued NOTIFY CHANGE 270 * request. If node is changed (reply == NODE_FLAGS_CHANGED), a normal reply is 271 * sent. 272 * If client cancels the request or session dropped, an NT_STATUS_CANCELED 273 * is sent in reply. 274 */ 275 276 void 277 smb_reply_notify_change_request(smb_request_t *sr) 278 { 279 smb_node_t *node; 280 int total_bytes, n_setup, n_param, n_data; 281 int param_off, param_pad, data_off, data_pad; 282 struct smb_xa *xa; 283 smb_error_t err; 284 285 xa = sr->r_xa; 286 node = sr->sr_ncr.nc_node; 287 288 if (--node->waiting_event == 0) { 289 node->flags &= ~(NODE_FLAGS_NOTIFY_CHANGE | NODE_FLAGS_CHANGED); 290 smb_fem_fcn_uninstall(node); 291 } 292 293 mutex_enter(&sr->sr_mutex); 294 switch (sr->sr_state) { 295 296 case SMB_REQ_STATE_EVENT_OCCURRED: 297 sr->sr_state = SMB_REQ_STATE_ACTIVE; 298 299 /* many things changed */ 300 301 (void) smb_mbc_encodef(&xa->rep_data_mb, "l", 0L); 302 303 /* setup the NT transact reply */ 304 305 n_setup = MBC_LENGTH(&xa->rep_setup_mb); 306 n_param = MBC_LENGTH(&xa->rep_param_mb); 307 n_data = MBC_LENGTH(&xa->rep_data_mb); 308 309 n_setup = (n_setup + 1) / 2; /* Convert to setup words */ 310 param_pad = 1; /* must be one */ 311 param_off = param_pad + 32 + 37 + (n_setup << 1) + 2; 312 /* Pad to 4 bytes */ 313 data_pad = (4 - ((param_off + n_param) & 3)) % 4; 314 /* Param off from hdr */ 315 data_off = param_off + n_param + data_pad; 316 total_bytes = param_pad + n_param + data_pad + n_data; 317 318 (void) smbsr_encode_result(sr, 18+n_setup, total_bytes, 319 "b3.llllllllbCw#.C#.C", 320 18 + n_setup, /* wct */ 321 n_param, /* Total Parameter Bytes */ 322 n_data, /* Total Data Bytes */ 323 n_param, /* Total Parameter Bytes this buffer */ 324 param_off, /* Param offset from header start */ 325 0, /* Param displacement */ 326 n_data, /* Total Data Bytes this buffer */ 327 data_off, /* Data offset from header start */ 328 0, /* Data displacement */ 329 n_setup, /* suwcnt */ 330 &xa->rep_setup_mb, /* setup[] */ 331 total_bytes, /* Total data bytes */ 332 param_pad, 333 &xa->rep_param_mb, 334 data_pad, 335 &xa->rep_data_mb); 336 break; 337 338 case SMB_REQ_STATE_CANCELED: 339 err.severity = ERROR_SEVERITY_ERROR; 340 err.status = NT_STATUS_CANCELLED; 341 err.errcls = ERRDOS; 342 err.errcode = ERROR_OPERATION_ABORTED; 343 smbsr_set_error(sr, &err); 344 345 (void) smb_mbc_encodef(&sr->reply, "bwbw", 346 (short)0, 0L, (short)0, 0L); 347 sr->smb_wct = 0; 348 sr->smb_bcc = 0; 349 break; 350 default: 351 ASSERT(0); 352 } 353 mutex_exit(&sr->sr_mutex); 354 355 /* Setup the header */ 356 (void) smb_mbc_poke(&sr->reply, 0, SMB_HEADER_ED_FMT, 357 sr->first_smb_com, 358 sr->smb_rcls, 359 sr->smb_reh, 360 sr->smb_err, 361 sr->smb_flg | SMB_FLAGS_REPLY, 362 sr->smb_flg2, 363 sr->smb_pid_high, 364 sr->smb_sig, 365 sr->smb_tid, 366 sr->smb_pid, 367 sr->smb_uid, 368 sr->smb_mid); 369 370 if (sr->session->signing.flags & SMB_SIGNING_ENABLED) 371 smb_sign_reply(sr, NULL); 372 373 /* send the reply */ 374 DTRACE_PROBE1(ncr__reply, struct smb_request *, sr) 375 (void) smb_session_send(sr->session, 0, &sr->reply); 376 smbsr_cleanup(sr); 377 378 mutex_enter(&sr->sr_mutex); 379 sr->sr_state = SMB_REQ_STATE_COMPLETED; 380 mutex_exit(&sr->sr_mutex); 381 smb_request_free(sr); 382 } 383 384 /* 385 * smb_process_session_notify_change_queue 386 * 387 * This function traverses notify change request queue and sends 388 * cancel replies to all of requests that are related to a specific 389 * session. 390 */ 391 void 392 smb_process_session_notify_change_queue( 393 smb_session_t *session, 394 smb_tree_t *tree) 395 { 396 smb_request_t *sr; 397 smb_request_t *tmp; 398 boolean_t sig = B_FALSE; 399 400 smb_slist_enter(&smb_ncr_list); 401 smb_slist_enter(&smb_nce_list); 402 sr = smb_slist_head(&smb_ncr_list); 403 while (sr) { 404 ASSERT(sr->sr_magic == SMB_REQ_MAGIC); 405 tmp = smb_slist_next(&smb_ncr_list, sr); 406 if ((sr->session == session) && 407 (tree == NULL || sr->tid_tree == tree)) { 408 mutex_enter(&sr->sr_mutex); 409 switch (sr->sr_state) { 410 case SMB_REQ_STATE_WAITING_EVENT: 411 smb_slist_obj_move( 412 &smb_nce_list, 413 &smb_ncr_list, 414 sr); 415 sr->sr_state = SMB_REQ_STATE_CANCELED; 416 sig = B_TRUE; 417 break; 418 default: 419 ASSERT(0); 420 break; 421 } 422 mutex_exit(&sr->sr_mutex); 423 } 424 sr = tmp; 425 } 426 smb_slist_exit(&smb_nce_list); 427 smb_slist_exit(&smb_ncr_list); 428 if (sig) 429 smb_thread_signal(&smb_thread_notify_daemon); 430 } 431 432 /* 433 * smb_process_file_notify_change_queue 434 * 435 * This function traverses notify change request queue and sends 436 * cancel replies to all of requests that are related to the 437 * specified file. 438 */ 439 void 440 smb_process_file_notify_change_queue(struct smb_ofile *of) 441 { 442 smb_request_t *sr; 443 smb_request_t *tmp; 444 boolean_t sig = B_FALSE; 445 446 smb_slist_enter(&smb_ncr_list); 447 smb_slist_enter(&smb_nce_list); 448 sr = smb_slist_head(&smb_ncr_list); 449 while (sr) { 450 ASSERT(sr->sr_magic == SMB_REQ_MAGIC); 451 tmp = smb_slist_next(&smb_ncr_list, sr); 452 if (sr->fid_ofile == of) { 453 mutex_enter(&sr->sr_mutex); 454 switch (sr->sr_state) { 455 case SMB_REQ_STATE_WAITING_EVENT: 456 smb_slist_obj_move(&smb_nce_list, 457 &smb_ncr_list, sr); 458 sr->sr_state = SMB_REQ_STATE_CANCELED; 459 sig = B_TRUE; 460 break; 461 default: 462 ASSERT(0); 463 break; 464 } 465 mutex_exit(&sr->sr_mutex); 466 } 467 sr = tmp; 468 } 469 smb_slist_exit(&smb_nce_list); 470 smb_slist_exit(&smb_ncr_list); 471 if (sig) 472 smb_thread_signal(&smb_thread_notify_daemon); 473 } 474 475 /* 476 * smb_reply_specific_cancel_request 477 * 478 * This function searches global request list for a specific request. If found, 479 * moves the request to event queue and kicks the notify change daemon. 480 */ 481 482 void 483 smb_reply_specific_cancel_request(struct smb_request *zsr) 484 { 485 smb_request_t *sr; 486 smb_request_t *tmp; 487 boolean_t sig = B_FALSE; 488 489 smb_slist_enter(&smb_ncr_list); 490 smb_slist_enter(&smb_nce_list); 491 sr = smb_slist_head(&smb_ncr_list); 492 while (sr) { 493 ASSERT(sr->sr_magic == SMB_REQ_MAGIC); 494 tmp = smb_slist_next(&smb_ncr_list, sr); 495 if ((sr->session == zsr->session) && 496 (sr->smb_uid == zsr->smb_uid) && 497 (sr->smb_pid == zsr->smb_pid) && 498 (sr->smb_tid == zsr->smb_tid) && 499 (sr->smb_mid == zsr->smb_mid)) { 500 mutex_enter(&sr->sr_mutex); 501 switch (sr->sr_state) { 502 case SMB_REQ_STATE_WAITING_EVENT: 503 smb_slist_obj_move(&smb_nce_list, 504 &smb_ncr_list, sr); 505 sr->sr_state = SMB_REQ_STATE_CANCELED; 506 sig = B_TRUE; 507 break; 508 default: 509 ASSERT(0); 510 break; 511 } 512 mutex_exit(&sr->sr_mutex); 513 } 514 sr = tmp; 515 } 516 smb_slist_exit(&smb_nce_list); 517 smb_slist_exit(&smb_ncr_list); 518 if (sig) 519 smb_thread_signal(&smb_thread_notify_daemon); 520 } 521 522 /* 523 * smb_process_node_notify_change_queue 524 * 525 * This function searches notify change request queue and sends 526 * 'NODE MODIFIED' reply to all requests which are related to a 527 * specific node. 528 * WatchTree flag: We handle this flag in a special manner just 529 * for DAVE clients. When something is changed, we notify all 530 * requests which came from DAVE clients on the same volume which 531 * has been modified. We don't care about the tree that they wanted 532 * us to monitor. any change in any part of the volume will lead 533 * to notifying all notify change requests from DAVE clients on the 534 * different parts of the volume hierarchy. 535 */ 536 void 537 smb_process_node_notify_change_queue(smb_node_t *node) 538 { 539 smb_request_t *sr; 540 smb_request_t *tmp; 541 boolean_t sig = B_FALSE; 542 543 ASSERT(node->n_magic == SMB_NODE_MAGIC); 544 545 if (!(node->flags & NODE_FLAGS_NOTIFY_CHANGE)) 546 return; 547 548 node->flags |= NODE_FLAGS_CHANGED; 549 550 smb_slist_enter(&smb_ncr_list); 551 smb_slist_enter(&smb_nce_list); 552 sr = smb_slist_head(&smb_ncr_list); 553 while (sr) { 554 ASSERT(sr->sr_magic == SMB_REQ_MAGIC); 555 tmp = smb_slist_next(&smb_ncr_list, sr); 556 557 if (smb_notify_change_required(sr, node)) { 558 mutex_enter(&sr->sr_mutex); 559 switch (sr->sr_state) { 560 case SMB_REQ_STATE_WAITING_EVENT: 561 smb_slist_obj_move(&smb_nce_list, 562 &smb_ncr_list, sr); 563 sr->sr_state = SMB_REQ_STATE_EVENT_OCCURRED; 564 sig = B_TRUE; 565 break; 566 default: 567 ASSERT(0); 568 break; 569 } 570 mutex_exit(&sr->sr_mutex); 571 } 572 sr = tmp; 573 } 574 smb_slist_exit(&smb_nce_list); 575 smb_slist_exit(&smb_ncr_list); 576 if (sig) 577 smb_thread_signal(&smb_thread_notify_daemon); 578 } 579 580 /* 581 * Change notification is required if: 582 * - the request node matches the specified node 583 * or 584 * - the request is from a Mac client, the watch-tree flag 585 * is set and it is monitoring a tree on the same volume. 586 */ 587 static boolean_t 588 smb_notify_change_required(smb_request_t *sr, smb_node_t *node) 589 { 590 smb_node_t *nc_node = sr->sr_ncr.nc_node; 591 592 if (nc_node == node) 593 return (B_TRUE); 594 595 if ((sr->sr_ncr.nc_flags & NODE_FLAGS_WATCH_TREE) && 596 (sr->session->native_os == NATIVE_OS_MACOS) && 597 smb_vfs_cmp(SMB_NODE_VFS(nc_node), SMB_NODE_VFS(node))) 598 return (B_TRUE); 599 600 return (B_FALSE); 601 } 602 603 /* 604 * smb_notify_change_daemon 605 * 606 * This function processes notify change event list and send appropriate 607 * responses to the requests. This function executes in the system as an 608 * indivdual thread. 609 */ 610 static void 611 smb_notify_change_daemon(smb_thread_t *thread, void *arg) 612 { 613 _NOTE(ARGUNUSED(arg)) 614 615 smb_request_t *sr; 616 smb_request_t *tmp; 617 list_t sr_list; 618 619 list_create(&sr_list, sizeof (smb_request_t), 620 offsetof(smb_request_t, sr_ncr.nc_lnd)); 621 622 while (smb_thread_continue(thread)) { 623 624 while (smb_slist_move_tail(&sr_list, &smb_nce_list)) { 625 sr = list_head(&sr_list); 626 while (sr) { 627 ASSERT(sr->sr_magic == SMB_REQ_MAGIC); 628 tmp = list_next(&sr_list, sr); 629 list_remove(&sr_list, sr); 630 smb_reply_notify_change_request(sr); 631 sr = tmp; 632 } 633 } 634 } 635 list_destroy(&sr_list); 636 } 637