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