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