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 */ 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 117 static boolean_t smb_notify_initialized = B_FALSE; 118 static smb_slist_t smb_ncr_list; 119 static smb_slist_t smb_nce_list; 120 static smb_thread_t smb_thread_notify_daemon; 121 122 /* 123 * smb_notify_init 124 * 125 * This function is not multi-thread safe. The caller must make sure only one 126 * thread makes the call. 127 */ 128 int 129 smb_notify_init(void) 130 { 131 int rc; 132 133 if (smb_notify_initialized) 134 return (0); 135 136 smb_slist_constructor(&smb_ncr_list, sizeof (smb_request_t), 137 offsetof(smb_request_t, sr_ncr.nc_lnd)); 138 139 smb_slist_constructor(&smb_nce_list, sizeof (smb_request_t), 140 offsetof(smb_request_t, sr_ncr.nc_lnd)); 141 142 smb_thread_init(&smb_thread_notify_daemon, 143 "smb_notify_change_daemon", smb_notify_change_daemon, NULL, 144 NULL, NULL); 145 146 rc = smb_thread_start(&smb_thread_notify_daemon); 147 if (rc) { 148 smb_thread_destroy(&smb_thread_notify_daemon); 149 smb_slist_destructor(&smb_ncr_list); 150 smb_slist_destructor(&smb_nce_list); 151 return (rc); 152 } 153 154 smb_notify_initialized = B_TRUE; 155 156 return (0); 157 } 158 159 /* 160 * smb_notify_fini 161 * 162 * This function is not multi-thread safe. The caller must make sure only one 163 * thread makes the call. 164 */ 165 void 166 smb_notify_fini(void) 167 { 168 if (!smb_notify_initialized) 169 return; 170 171 smb_thread_stop(&smb_thread_notify_daemon); 172 smb_thread_destroy(&smb_thread_notify_daemon); 173 smb_slist_destructor(&smb_ncr_list); 174 smb_slist_destructor(&smb_nce_list); 175 smb_notify_initialized = B_FALSE; 176 } 177 178 /* 179 * smb_nt_transact_notify_change 180 * 181 * This function is responsible for processing NOTIFY CHANGE requests. 182 * Requests are stored in a global queue. This queue is processed when 183 * a monitored directory is changed or client cancels one of its already 184 * sent requests. 185 */ 186 smb_sdrc_t 187 smb_nt_transact_notify_change(struct smb_request *sr, struct smb_xa *xa) 188 { 189 uint32_t CompletionFilter; 190 unsigned char WatchTree; 191 smb_node_t *node; 192 193 if (smb_mbc_decodef(&xa->req_setup_mb, "lwb", 194 &CompletionFilter, &sr->smb_fid, &WatchTree) != 0) 195 return (SDRC_NOT_IMPLEMENTED); 196 197 smbsr_lookup_file(sr); 198 if (sr->fid_ofile == NULL) { 199 smbsr_error(sr, NT_STATUS_INVALID_HANDLE, ERRDOS, ERRbadfid); 200 return (SDRC_ERROR); 201 } 202 203 node = sr->fid_ofile->f_node; 204 205 if (!smb_node_is_dir(node)) { 206 /* 207 * Notify change requests are only valid on directories. 208 */ 209 smbsr_error(sr, NT_STATUS_NOT_A_DIRECTORY, 0, 0); 210 return (SDRC_ERROR); 211 } 212 213 mutex_enter(&sr->sr_mutex); 214 switch (sr->sr_state) { 215 case SMB_REQ_STATE_ACTIVE: 216 node->waiting_event++; 217 node->flags |= NODE_FLAGS_NOTIFY_CHANGE; 218 if ((node->flags & NODE_FLAGS_CHANGED) == 0) { 219 sr->sr_ncr.nc_node = node; 220 sr->sr_ncr.nc_flags = CompletionFilter; 221 if (WatchTree) 222 sr->sr_ncr.nc_flags |= NODE_FLAGS_WATCH_TREE; 223 224 sr->sr_keep = B_TRUE; 225 sr->sr_state = SMB_REQ_STATE_WAITING_EVENT; 226 227 smb_slist_insert_tail(&smb_ncr_list, sr); 228 229 /* 230 * Monitor events system-wide. 231 * 232 * XXX: smb_node_ref() and smb_node_release() 233 * take &node->n_lock. May need alternate forms 234 * of these routines if node->n_lock is taken 235 * around calls to smb_fem_fcn_install() and 236 * smb_fem_fcn_uninstall(). 237 */ 238 239 smb_fem_fcn_install(node); 240 241 mutex_exit(&sr->sr_mutex); 242 return (SDRC_SR_KEPT); 243 } else { 244 /* node already changed, reply immediately */ 245 if (--node->waiting_event == 0) 246 node->flags &= 247 ~(NODE_FLAGS_NOTIFY_CHANGE | 248 NODE_FLAGS_CHANGED); 249 mutex_exit(&sr->sr_mutex); 250 return (SDRC_SUCCESS); 251 } 252 253 case SMB_REQ_STATE_CANCELED: 254 mutex_exit(&sr->sr_mutex); 255 smbsr_error(sr, NT_STATUS_CANCELLED, 0, 0); 256 return (SDRC_ERROR); 257 258 default: 259 ASSERT(0); 260 mutex_exit(&sr->sr_mutex); 261 return (SDRC_SUCCESS); 262 } 263 } 264 265 /* 266 * smb_reply_notify_change_request 267 * 268 * This function sends appropriate response to an already queued NOTIFY CHANGE 269 * request. If node is changed (reply == NODE_FLAGS_CHANGED), a normal reply is 270 * sent. 271 * If client cancels the request or session dropped, an NT_STATUS_CANCELED 272 * is sent in reply. 273 */ 274 275 void 276 smb_reply_notify_change_request(smb_request_t *sr) 277 { 278 smb_node_t *node; 279 smb_srqueue_t *srq; 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 SMB_REQ_VALID(sr); 286 srq = sr->session->s_srqueue; 287 smb_srqueue_waitq_to_runq(srq); 288 289 xa = sr->r_xa; 290 node = sr->sr_ncr.nc_node; 291 292 if (--node->waiting_event == 0) { 293 node->flags &= ~(NODE_FLAGS_NOTIFY_CHANGE | NODE_FLAGS_CHANGED); 294 smb_fem_fcn_uninstall(node); 295 } 296 297 mutex_enter(&sr->sr_mutex); 298 switch (sr->sr_state) { 299 300 case SMB_REQ_STATE_EVENT_OCCURRED: 301 sr->sr_state = SMB_REQ_STATE_ACTIVE; 302 303 /* many things changed */ 304 305 (void) smb_mbc_encodef(&xa->rep_data_mb, "l", 0L); 306 307 /* setup the NT transact reply */ 308 309 n_setup = MBC_LENGTH(&xa->rep_setup_mb); 310 n_param = MBC_LENGTH(&xa->rep_param_mb); 311 n_data = MBC_LENGTH(&xa->rep_data_mb); 312 313 n_setup = (n_setup + 1) / 2; /* Convert to setup words */ 314 param_pad = 1; /* must be one */ 315 param_off = param_pad + 32 + 37 + (n_setup << 1) + 2; 316 /* Pad to 4 bytes */ 317 data_pad = (4 - ((param_off + n_param) & 3)) % 4; 318 /* Param off from hdr */ 319 data_off = param_off + n_param + data_pad; 320 total_bytes = param_pad + n_param + data_pad + n_data; 321 322 (void) smbsr_encode_result(sr, 18+n_setup, total_bytes, 323 "b3.llllllllbCw#.C#.C", 324 18 + n_setup, /* wct */ 325 n_param, /* Total Parameter Bytes */ 326 n_data, /* Total Data Bytes */ 327 n_param, /* Total Parameter Bytes this buffer */ 328 param_off, /* Param offset from header start */ 329 0, /* Param displacement */ 330 n_data, /* Total Data Bytes this buffer */ 331 data_off, /* Data offset from header start */ 332 0, /* Data displacement */ 333 n_setup, /* suwcnt */ 334 &xa->rep_setup_mb, /* setup[] */ 335 total_bytes, /* Total data bytes */ 336 param_pad, 337 &xa->rep_param_mb, 338 data_pad, 339 &xa->rep_data_mb); 340 break; 341 342 case SMB_REQ_STATE_CANCELED: 343 err.status = NT_STATUS_CANCELLED; 344 err.errcls = ERRDOS; 345 err.errcode = ERROR_OPERATION_ABORTED; 346 smbsr_set_error(sr, &err); 347 348 (void) smb_mbc_encodef(&sr->reply, "bwbw", 349 (short)0, 0L, (short)0, 0L); 350 sr->smb_wct = 0; 351 sr->smb_bcc = 0; 352 break; 353 default: 354 ASSERT(0); 355 } 356 mutex_exit(&sr->sr_mutex); 357 358 /* Setup the header */ 359 (void) smb_mbc_poke(&sr->reply, 0, SMB_HEADER_ED_FMT, 360 sr->first_smb_com, 361 sr->smb_rcls, 362 sr->smb_reh, 363 sr->smb_err, 364 sr->smb_flg | SMB_FLAGS_REPLY, 365 sr->smb_flg2, 366 sr->smb_pid_high, 367 sr->smb_sig, 368 sr->smb_tid, 369 sr->smb_pid, 370 sr->smb_uid, 371 sr->smb_mid); 372 373 if (sr->session->signing.flags & SMB_SIGNING_ENABLED) 374 smb_sign_reply(sr, NULL); 375 376 /* send the reply */ 377 DTRACE_PROBE1(ncr__reply, struct smb_request *, sr) 378 (void) smb_session_send(sr->session, 0, &sr->reply); 379 smbsr_cleanup(sr); 380 381 mutex_enter(&sr->sr_mutex); 382 sr->sr_state = SMB_REQ_STATE_COMPLETED; 383 mutex_exit(&sr->sr_mutex); 384 smb_srqueue_runq_exit(srq); 385 smb_request_free(sr); 386 } 387 388 /* 389 * smb_process_session_notify_change_queue 390 * 391 * This function traverses notify change request queue and sends 392 * cancel replies to all of requests that are related to a specific 393 * session. 394 */ 395 void 396 smb_process_session_notify_change_queue( 397 smb_session_t *session, 398 smb_tree_t *tree) 399 { 400 smb_request_t *sr; 401 smb_request_t *tmp; 402 boolean_t sig = B_FALSE; 403 404 smb_slist_enter(&smb_ncr_list); 405 smb_slist_enter(&smb_nce_list); 406 sr = smb_slist_head(&smb_ncr_list); 407 while (sr) { 408 ASSERT(sr->sr_magic == SMB_REQ_MAGIC); 409 tmp = smb_slist_next(&smb_ncr_list, sr); 410 if ((sr->session == session) && 411 (tree == NULL || sr->tid_tree == tree)) { 412 mutex_enter(&sr->sr_mutex); 413 switch (sr->sr_state) { 414 case SMB_REQ_STATE_WAITING_EVENT: 415 smb_slist_obj_move( 416 &smb_nce_list, 417 &smb_ncr_list, 418 sr); 419 smb_srqueue_waitq_enter( 420 sr->session->s_srqueue); 421 sr->sr_state = SMB_REQ_STATE_CANCELED; 422 sig = B_TRUE; 423 break; 424 default: 425 ASSERT(0); 426 break; 427 } 428 mutex_exit(&sr->sr_mutex); 429 } 430 sr = tmp; 431 } 432 smb_slist_exit(&smb_nce_list); 433 smb_slist_exit(&smb_ncr_list); 434 if (sig) 435 smb_thread_signal(&smb_thread_notify_daemon); 436 } 437 438 /* 439 * smb_process_file_notify_change_queue 440 * 441 * This function traverses notify change request queue and sends 442 * cancel replies to all of requests that are related to the 443 * specified file. 444 */ 445 void 446 smb_process_file_notify_change_queue(struct smb_ofile *of) 447 { 448 smb_request_t *sr; 449 smb_request_t *tmp; 450 boolean_t sig = B_FALSE; 451 452 smb_slist_enter(&smb_ncr_list); 453 smb_slist_enter(&smb_nce_list); 454 sr = smb_slist_head(&smb_ncr_list); 455 while (sr) { 456 ASSERT(sr->sr_magic == SMB_REQ_MAGIC); 457 tmp = smb_slist_next(&smb_ncr_list, sr); 458 if (sr->fid_ofile == of) { 459 mutex_enter(&sr->sr_mutex); 460 switch (sr->sr_state) { 461 case SMB_REQ_STATE_WAITING_EVENT: 462 smb_slist_obj_move(&smb_nce_list, 463 &smb_ncr_list, sr); 464 smb_srqueue_waitq_enter( 465 sr->session->s_srqueue); 466 sr->sr_state = SMB_REQ_STATE_CANCELED; 467 sig = B_TRUE; 468 break; 469 default: 470 ASSERT(0); 471 break; 472 } 473 mutex_exit(&sr->sr_mutex); 474 } 475 sr = tmp; 476 } 477 smb_slist_exit(&smb_nce_list); 478 smb_slist_exit(&smb_ncr_list); 479 if (sig) 480 smb_thread_signal(&smb_thread_notify_daemon); 481 } 482 483 /* 484 * smb_reply_specific_cancel_request 485 * 486 * This function searches global request list for a specific request. If found, 487 * moves the request to event queue and kicks the notify change daemon. 488 */ 489 490 void 491 smb_reply_specific_cancel_request(struct smb_request *zsr) 492 { 493 smb_request_t *sr; 494 smb_request_t *tmp; 495 boolean_t sig = B_FALSE; 496 497 smb_slist_enter(&smb_ncr_list); 498 smb_slist_enter(&smb_nce_list); 499 sr = smb_slist_head(&smb_ncr_list); 500 while (sr) { 501 ASSERT(sr->sr_magic == SMB_REQ_MAGIC); 502 tmp = smb_slist_next(&smb_ncr_list, sr); 503 if ((sr->session == zsr->session) && 504 (sr->smb_uid == zsr->smb_uid) && 505 (sr->smb_pid == zsr->smb_pid) && 506 (sr->smb_tid == zsr->smb_tid) && 507 (sr->smb_mid == zsr->smb_mid)) { 508 mutex_enter(&sr->sr_mutex); 509 switch (sr->sr_state) { 510 case SMB_REQ_STATE_WAITING_EVENT: 511 smb_slist_obj_move(&smb_nce_list, 512 &smb_ncr_list, sr); 513 smb_srqueue_waitq_enter( 514 sr->session->s_srqueue); 515 sr->sr_state = SMB_REQ_STATE_CANCELED; 516 sig = B_TRUE; 517 break; 518 default: 519 ASSERT(0); 520 break; 521 } 522 mutex_exit(&sr->sr_mutex); 523 } 524 sr = tmp; 525 } 526 smb_slist_exit(&smb_nce_list); 527 smb_slist_exit(&smb_ncr_list); 528 if (sig) 529 smb_thread_signal(&smb_thread_notify_daemon); 530 } 531 532 /* 533 * smb_process_node_notify_change_queue 534 * 535 * This function searches notify change request queue and sends 536 * 'NODE MODIFIED' reply to all requests which are related to a 537 * specific node. 538 * WatchTree flag: We handle this flag in a special manner just 539 * for DAVE clients. When something is changed, we notify all 540 * requests which came from DAVE clients on the same volume which 541 * has been modified. We don't care about the tree that they wanted 542 * us to monitor. any change in any part of the volume will lead 543 * to notifying all notify change requests from DAVE clients on the 544 * different parts of the volume hierarchy. 545 */ 546 void 547 smb_process_node_notify_change_queue(smb_node_t *node) 548 { 549 smb_request_t *sr; 550 smb_request_t *tmp; 551 smb_node_t *nc_node; 552 boolean_t sig = B_FALSE; 553 554 ASSERT(node->n_magic == SMB_NODE_MAGIC); 555 556 if (!(node->flags & NODE_FLAGS_NOTIFY_CHANGE)) 557 return; 558 559 node->flags |= NODE_FLAGS_CHANGED; 560 561 smb_slist_enter(&smb_ncr_list); 562 smb_slist_enter(&smb_nce_list); 563 sr = smb_slist_head(&smb_ncr_list); 564 while (sr) { 565 ASSERT(sr->sr_magic == SMB_REQ_MAGIC); 566 tmp = smb_slist_next(&smb_ncr_list, sr); 567 568 nc_node = sr->sr_ncr.nc_node; 569 if (nc_node == node) { 570 mutex_enter(&sr->sr_mutex); 571 switch (sr->sr_state) { 572 case SMB_REQ_STATE_WAITING_EVENT: 573 smb_slist_obj_move(&smb_nce_list, 574 &smb_ncr_list, sr); 575 smb_srqueue_waitq_enter( 576 sr->session->s_srqueue); 577 sr->sr_state = SMB_REQ_STATE_EVENT_OCCURRED; 578 sig = B_TRUE; 579 break; 580 default: 581 ASSERT(0); 582 break; 583 } 584 mutex_exit(&sr->sr_mutex); 585 } 586 sr = tmp; 587 } 588 smb_slist_exit(&smb_nce_list); 589 smb_slist_exit(&smb_ncr_list); 590 if (sig) 591 smb_thread_signal(&smb_thread_notify_daemon); 592 } 593 594 /* 595 * smb_notify_change_daemon 596 * 597 * This function processes notify change event list and send appropriate 598 * responses to the requests. This function executes in the system as an 599 * indivdual thread. 600 */ 601 static void 602 smb_notify_change_daemon(smb_thread_t *thread, void *arg) 603 { 604 _NOTE(ARGUNUSED(arg)) 605 606 smb_request_t *sr; 607 smb_request_t *tmp; 608 list_t sr_list; 609 610 list_create(&sr_list, sizeof (smb_request_t), 611 offsetof(smb_request_t, sr_ncr.nc_lnd)); 612 613 while (smb_thread_continue(thread)) { 614 615 while (smb_slist_move_tail(&sr_list, &smb_nce_list)) { 616 sr = list_head(&sr_list); 617 while (sr) { 618 ASSERT(sr->sr_magic == SMB_REQ_MAGIC); 619 tmp = list_next(&sr_list, sr); 620 list_remove(&sr_list, sr); 621 smb_reply_notify_change_request(sr); 622 sr = tmp; 623 } 624 } 625 } 626 list_destroy(&sr_list); 627 } 628