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 2010 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 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 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 xa = sr->r_xa; 285 node = sr->sr_ncr.nc_node; 286 287 if (--node->waiting_event == 0) { 288 node->flags &= ~(NODE_FLAGS_NOTIFY_CHANGE | NODE_FLAGS_CHANGED); 289 smb_fem_fcn_uninstall(node); 290 } 291 292 mutex_enter(&sr->sr_mutex); 293 switch (sr->sr_state) { 294 295 case SMB_REQ_STATE_EVENT_OCCURRED: 296 sr->sr_state = SMB_REQ_STATE_ACTIVE; 297 298 /* many things changed */ 299 300 (void) smb_mbc_encodef(&xa->rep_data_mb, "l", 0L); 301 302 /* setup the NT transact reply */ 303 304 n_setup = MBC_LENGTH(&xa->rep_setup_mb); 305 n_param = MBC_LENGTH(&xa->rep_param_mb); 306 n_data = MBC_LENGTH(&xa->rep_data_mb); 307 308 n_setup = (n_setup + 1) / 2; /* Convert to setup words */ 309 param_pad = 1; /* must be one */ 310 param_off = param_pad + 32 + 37 + (n_setup << 1) + 2; 311 /* Pad to 4 bytes */ 312 data_pad = (4 - ((param_off + n_param) & 3)) % 4; 313 /* Param off from hdr */ 314 data_off = param_off + n_param + data_pad; 315 total_bytes = param_pad + n_param + data_pad + n_data; 316 317 (void) smbsr_encode_result(sr, 18+n_setup, total_bytes, 318 "b3.llllllllbCw#.C#.C", 319 18 + n_setup, /* wct */ 320 n_param, /* Total Parameter Bytes */ 321 n_data, /* Total Data Bytes */ 322 n_param, /* Total Parameter Bytes this buffer */ 323 param_off, /* Param offset from header start */ 324 0, /* Param displacement */ 325 n_data, /* Total Data Bytes this buffer */ 326 data_off, /* Data offset from header start */ 327 0, /* Data displacement */ 328 n_setup, /* suwcnt */ 329 &xa->rep_setup_mb, /* setup[] */ 330 total_bytes, /* Total data bytes */ 331 param_pad, 332 &xa->rep_param_mb, 333 data_pad, 334 &xa->rep_data_mb); 335 break; 336 337 case SMB_REQ_STATE_CANCELED: 338 err.severity = ERROR_SEVERITY_ERROR; 339 err.status = NT_STATUS_CANCELLED; 340 err.errcls = ERRDOS; 341 err.errcode = ERROR_OPERATION_ABORTED; 342 smbsr_set_error(sr, &err); 343 344 (void) smb_mbc_encodef(&sr->reply, "bwbw", 345 (short)0, 0L, (short)0, 0L); 346 sr->smb_wct = 0; 347 sr->smb_bcc = 0; 348 break; 349 default: 350 ASSERT(0); 351 } 352 mutex_exit(&sr->sr_mutex); 353 354 /* Setup the header */ 355 (void) smb_mbc_poke(&sr->reply, 0, SMB_HEADER_ED_FMT, 356 sr->first_smb_com, 357 sr->smb_rcls, 358 sr->smb_reh, 359 sr->smb_err, 360 sr->smb_flg | SMB_FLAGS_REPLY, 361 sr->smb_flg2, 362 sr->smb_pid_high, 363 sr->smb_sig, 364 sr->smb_tid, 365 sr->smb_pid, 366 sr->smb_uid, 367 sr->smb_mid); 368 369 if (sr->session->signing.flags & SMB_SIGNING_ENABLED) 370 smb_sign_reply(sr, NULL); 371 372 /* send the reply */ 373 DTRACE_PROBE1(ncr__reply, struct smb_request *, sr) 374 (void) smb_session_send(sr->session, 0, &sr->reply); 375 smbsr_cleanup(sr); 376 377 mutex_enter(&sr->sr_mutex); 378 sr->sr_state = SMB_REQ_STATE_COMPLETED; 379 mutex_exit(&sr->sr_mutex); 380 smb_request_free(sr); 381 } 382 383 /* 384 * smb_process_session_notify_change_queue 385 * 386 * This function traverses notify change request queue and sends 387 * cancel replies to all of requests that are related to a specific 388 * session. 389 */ 390 void 391 smb_process_session_notify_change_queue( 392 smb_session_t *session, 393 smb_tree_t *tree) 394 { 395 smb_request_t *sr; 396 smb_request_t *tmp; 397 boolean_t sig = B_FALSE; 398 399 smb_slist_enter(&smb_ncr_list); 400 smb_slist_enter(&smb_nce_list); 401 sr = smb_slist_head(&smb_ncr_list); 402 while (sr) { 403 ASSERT(sr->sr_magic == SMB_REQ_MAGIC); 404 tmp = smb_slist_next(&smb_ncr_list, sr); 405 if ((sr->session == session) && 406 (tree == NULL || sr->tid_tree == tree)) { 407 mutex_enter(&sr->sr_mutex); 408 switch (sr->sr_state) { 409 case SMB_REQ_STATE_WAITING_EVENT: 410 smb_slist_obj_move( 411 &smb_nce_list, 412 &smb_ncr_list, 413 sr); 414 sr->sr_state = SMB_REQ_STATE_CANCELED; 415 sig = B_TRUE; 416 break; 417 default: 418 ASSERT(0); 419 break; 420 } 421 mutex_exit(&sr->sr_mutex); 422 } 423 sr = tmp; 424 } 425 smb_slist_exit(&smb_nce_list); 426 smb_slist_exit(&smb_ncr_list); 427 if (sig) 428 smb_thread_signal(&smb_thread_notify_daemon); 429 } 430 431 /* 432 * smb_process_file_notify_change_queue 433 * 434 * This function traverses notify change request queue and sends 435 * cancel replies to all of requests that are related to the 436 * specified file. 437 */ 438 void 439 smb_process_file_notify_change_queue(struct smb_ofile *of) 440 { 441 smb_request_t *sr; 442 smb_request_t *tmp; 443 boolean_t sig = B_FALSE; 444 445 smb_slist_enter(&smb_ncr_list); 446 smb_slist_enter(&smb_nce_list); 447 sr = smb_slist_head(&smb_ncr_list); 448 while (sr) { 449 ASSERT(sr->sr_magic == SMB_REQ_MAGIC); 450 tmp = smb_slist_next(&smb_ncr_list, sr); 451 if (sr->fid_ofile == of) { 452 mutex_enter(&sr->sr_mutex); 453 switch (sr->sr_state) { 454 case SMB_REQ_STATE_WAITING_EVENT: 455 smb_slist_obj_move(&smb_nce_list, 456 &smb_ncr_list, sr); 457 sr->sr_state = SMB_REQ_STATE_CANCELED; 458 sig = B_TRUE; 459 break; 460 default: 461 ASSERT(0); 462 break; 463 } 464 mutex_exit(&sr->sr_mutex); 465 } 466 sr = tmp; 467 } 468 smb_slist_exit(&smb_nce_list); 469 smb_slist_exit(&smb_ncr_list); 470 if (sig) 471 smb_thread_signal(&smb_thread_notify_daemon); 472 } 473 474 /* 475 * smb_reply_specific_cancel_request 476 * 477 * This function searches global request list for a specific request. If found, 478 * moves the request to event queue and kicks the notify change daemon. 479 */ 480 481 void 482 smb_reply_specific_cancel_request(struct smb_request *zsr) 483 { 484 smb_request_t *sr; 485 smb_request_t *tmp; 486 boolean_t sig = B_FALSE; 487 488 smb_slist_enter(&smb_ncr_list); 489 smb_slist_enter(&smb_nce_list); 490 sr = smb_slist_head(&smb_ncr_list); 491 while (sr) { 492 ASSERT(sr->sr_magic == SMB_REQ_MAGIC); 493 tmp = smb_slist_next(&smb_ncr_list, sr); 494 if ((sr->session == zsr->session) && 495 (sr->smb_uid == zsr->smb_uid) && 496 (sr->smb_pid == zsr->smb_pid) && 497 (sr->smb_tid == zsr->smb_tid) && 498 (sr->smb_mid == zsr->smb_mid)) { 499 mutex_enter(&sr->sr_mutex); 500 switch (sr->sr_state) { 501 case SMB_REQ_STATE_WAITING_EVENT: 502 smb_slist_obj_move(&smb_nce_list, 503 &smb_ncr_list, sr); 504 sr->sr_state = SMB_REQ_STATE_CANCELED; 505 sig = B_TRUE; 506 break; 507 default: 508 ASSERT(0); 509 break; 510 } 511 mutex_exit(&sr->sr_mutex); 512 } 513 sr = tmp; 514 } 515 smb_slist_exit(&smb_nce_list); 516 smb_slist_exit(&smb_ncr_list); 517 if (sig) 518 smb_thread_signal(&smb_thread_notify_daemon); 519 } 520 521 /* 522 * smb_process_node_notify_change_queue 523 * 524 * This function searches notify change request queue and sends 525 * 'NODE MODIFIED' reply to all requests which are related to a 526 * specific node. 527 * WatchTree flag: We handle this flag in a special manner just 528 * for DAVE clients. When something is changed, we notify all 529 * requests which came from DAVE clients on the same volume which 530 * has been modified. We don't care about the tree that they wanted 531 * us to monitor. any change in any part of the volume will lead 532 * to notifying all notify change requests from DAVE clients on the 533 * different parts of the volume hierarchy. 534 */ 535 void 536 smb_process_node_notify_change_queue(smb_node_t *node) 537 { 538 smb_request_t *sr; 539 smb_request_t *tmp; 540 smb_node_t *nc_node; 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 nc_node = sr->sr_ncr.nc_node; 558 if (nc_node == node) { 559 mutex_enter(&sr->sr_mutex); 560 switch (sr->sr_state) { 561 case SMB_REQ_STATE_WAITING_EVENT: 562 smb_slist_obj_move(&smb_nce_list, 563 &smb_ncr_list, sr); 564 sr->sr_state = SMB_REQ_STATE_EVENT_OCCURRED; 565 sig = B_TRUE; 566 break; 567 default: 568 ASSERT(0); 569 break; 570 } 571 mutex_exit(&sr->sr_mutex); 572 } 573 sr = tmp; 574 } 575 smb_slist_exit(&smb_nce_list); 576 smb_slist_exit(&smb_ncr_list); 577 if (sig) 578 smb_thread_signal(&smb_thread_notify_daemon); 579 } 580 581 /* 582 * smb_notify_change_daemon 583 * 584 * This function processes notify change event list and send appropriate 585 * responses to the requests. This function executes in the system as an 586 * indivdual thread. 587 */ 588 static void 589 smb_notify_change_daemon(smb_thread_t *thread, void *arg) 590 { 591 _NOTE(ARGUNUSED(arg)) 592 593 smb_request_t *sr; 594 smb_request_t *tmp; 595 list_t sr_list; 596 597 list_create(&sr_list, sizeof (smb_request_t), 598 offsetof(smb_request_t, sr_ncr.nc_lnd)); 599 600 while (smb_thread_continue(thread)) { 601 602 while (smb_slist_move_tail(&sr_list, &smb_nce_list)) { 603 sr = list_head(&sr_list); 604 while (sr) { 605 ASSERT(sr->sr_magic == SMB_REQ_MAGIC); 606 tmp = list_next(&sr_list, sr); 607 list_remove(&sr_list, sr); 608 smb_reply_notify_change_request(sr); 609 sr = tmp; 610 } 611 } 612 } 613 list_destroy(&sr_list); 614 } 615