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 "%Z%%M% %I% %E% 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_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 sr->fid_ofile = smb_ofile_lookup_by_fid(sr->tid_tree, sr->smb_fid); 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 (node->attr.sa_vattr.va_type != VDIR) { 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_NO_REPLY); 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(struct smb_session *session) 393 { 394 smb_request_t *sr; 395 smb_request_t *tmp; 396 boolean_t sig = B_FALSE; 397 398 smb_slist_enter(&smb_ncr_list); 399 smb_slist_enter(&smb_nce_list); 400 sr = smb_slist_head(&smb_ncr_list); 401 while (sr) { 402 ASSERT(sr->sr_magic == SMB_REQ_MAGIC); 403 tmp = smb_slist_next(&smb_ncr_list, sr); 404 if (sr->session == session) { 405 mutex_enter(&sr->sr_mutex); 406 switch (sr->sr_state) { 407 case SMB_REQ_STATE_WAITING_EVENT: 408 smb_slist_obj_move( 409 &smb_nce_list, 410 &smb_ncr_list, 411 sr); 412 sr->sr_state = SMB_REQ_STATE_CANCELED; 413 sig = B_TRUE; 414 break; 415 default: 416 ASSERT(0); 417 break; 418 } 419 mutex_exit(&sr->sr_mutex); 420 } 421 sr = tmp; 422 } 423 smb_slist_exit(&smb_nce_list); 424 smb_slist_exit(&smb_ncr_list); 425 if (sig) 426 smb_thread_signal(&smb_thread_notify_daemon); 427 } 428 429 /* 430 * smb_process_file_notify_change_queue 431 * 432 * This function traverses notify change request queue and sends 433 * cancel replies to all of requests that are related to the 434 * specified file. 435 */ 436 void 437 smb_process_file_notify_change_queue(struct smb_ofile *of) 438 { 439 smb_request_t *sr; 440 smb_request_t *tmp; 441 boolean_t sig = B_FALSE; 442 443 smb_slist_enter(&smb_ncr_list); 444 smb_slist_enter(&smb_nce_list); 445 sr = smb_slist_head(&smb_ncr_list); 446 while (sr) { 447 ASSERT(sr->sr_magic == SMB_REQ_MAGIC); 448 tmp = smb_slist_next(&smb_ncr_list, sr); 449 if (sr->fid_ofile == of) { 450 mutex_enter(&sr->sr_mutex); 451 switch (sr->sr_state) { 452 case SMB_REQ_STATE_WAITING_EVENT: 453 smb_slist_obj_move(&smb_nce_list, 454 &smb_ncr_list, sr); 455 sr->sr_state = SMB_REQ_STATE_CANCELED; 456 sig = B_TRUE; 457 break; 458 default: 459 ASSERT(0); 460 break; 461 } 462 mutex_exit(&sr->sr_mutex); 463 } 464 sr = tmp; 465 } 466 smb_slist_exit(&smb_nce_list); 467 smb_slist_exit(&smb_ncr_list); 468 if (sig) 469 smb_thread_signal(&smb_thread_notify_daemon); 470 } 471 472 /* 473 * smb_reply_specific_cancel_request 474 * 475 * This function searches global request list for a specific request. If found, 476 * moves the request to event queue and kicks the notify change daemon. 477 */ 478 479 void 480 smb_reply_specific_cancel_request(struct smb_request *zsr) 481 { 482 smb_request_t *sr; 483 smb_request_t *tmp; 484 boolean_t sig = B_FALSE; 485 486 smb_slist_enter(&smb_ncr_list); 487 smb_slist_enter(&smb_nce_list); 488 sr = smb_slist_head(&smb_ncr_list); 489 while (sr) { 490 ASSERT(sr->sr_magic == SMB_REQ_MAGIC); 491 tmp = smb_slist_next(&smb_ncr_list, sr); 492 if ((sr->session == zsr->session) && 493 (sr->smb_sid == zsr->smb_sid) && 494 (sr->smb_uid == zsr->smb_uid) && 495 (sr->smb_pid == zsr->smb_pid) && 496 (sr->smb_tid == zsr->smb_tid) && 497 (sr->smb_mid == zsr->smb_mid)) { 498 mutex_enter(&sr->sr_mutex); 499 switch (sr->sr_state) { 500 case SMB_REQ_STATE_WAITING_EVENT: 501 smb_slist_obj_move(&smb_nce_list, 502 &smb_ncr_list, sr); 503 sr->sr_state = SMB_REQ_STATE_CANCELED; 504 sig = B_TRUE; 505 break; 506 default: 507 ASSERT(0); 508 break; 509 } 510 mutex_exit(&sr->sr_mutex); 511 } 512 sr = tmp; 513 } 514 smb_slist_exit(&smb_nce_list); 515 smb_slist_exit(&smb_ncr_list); 516 if (sig) 517 smb_thread_signal(&smb_thread_notify_daemon); 518 } 519 520 /* 521 * smb_process_node_notify_change_queue 522 * 523 * This function searches notify change request queue and sends 524 * 'NODE MODIFIED' reply to all requests which are related to a 525 * specific node. 526 * WatchTree flag: We handle this flag in a special manner just 527 * for DAVE clients. When something is changed, we notify all 528 * requests which came from DAVE clients on the same volume which 529 * has been modified. We don't care about the tree that they wanted 530 * us to monitor. any change in any part of the volume will lead 531 * to notifying all notify change requests from DAVE clients on the 532 * different parts of the volume hierarchy. 533 */ 534 void 535 smb_process_node_notify_change_queue(smb_node_t *node) 536 { 537 smb_request_t *sr; 538 smb_request_t *tmp; 539 boolean_t sig = B_FALSE; 540 541 ASSERT(node->n_magic == SMB_NODE_MAGIC); 542 543 if (!(node->flags & NODE_FLAGS_NOTIFY_CHANGE)) 544 return; 545 546 node->flags |= NODE_FLAGS_CHANGED; 547 548 smb_slist_enter(&smb_ncr_list); 549 smb_slist_enter(&smb_nce_list); 550 sr = smb_slist_head(&smb_ncr_list); 551 while (sr) { 552 ASSERT(sr->sr_magic == SMB_REQ_MAGIC); 553 tmp = smb_slist_next(&smb_ncr_list, sr); 554 /* 555 * send notify if: 556 * - it's a request for the same node or 557 * - it's a request from a DAVE client, its 'watch tree' 558 * flag is set and monitors a tree on the same volume. 559 */ 560 if ((sr->sr_ncr.nc_node == node) || 561 ((sr->sr_ncr.nc_flags & NODE_FLAGS_WATCH_TREE) && 562 (sr->session->native_os == NATIVE_OS_MACOS) && 563 !fsd_cmp(&sr->sr_ncr.nc_node->tree_fsd, &node->tree_fsd))) { 564 mutex_enter(&sr->sr_mutex); 565 switch (sr->sr_state) { 566 case SMB_REQ_STATE_WAITING_EVENT: 567 smb_slist_obj_move(&smb_nce_list, 568 &smb_ncr_list, sr); 569 sr->sr_state = SMB_REQ_STATE_EVENT_OCCURRED; 570 sig = B_TRUE; 571 break; 572 default: 573 ASSERT(0); 574 break; 575 } 576 mutex_exit(&sr->sr_mutex); 577 } 578 sr = tmp; 579 } 580 smb_slist_exit(&smb_nce_list); 581 smb_slist_exit(&smb_ncr_list); 582 if (sig) 583 smb_thread_signal(&smb_thread_notify_daemon); 584 } 585 586 /* 587 * smb_notify_change_daemon 588 * 589 * This function processes notify change event list and send appropriate 590 * responses to the requests. This function executes in the system as an 591 * indivdual thread. 592 */ 593 static void 594 smb_notify_change_daemon(smb_thread_t *thread, void *arg) 595 { 596 _NOTE(ARGUNUSED(arg)) 597 598 smb_request_t *sr; 599 smb_request_t *tmp; 600 list_t sr_list; 601 602 list_create(&sr_list, sizeof (smb_request_t), 603 offsetof(smb_request_t, sr_ncr.nc_lnd)); 604 605 while (smb_thread_continue(thread)) { 606 607 while (smb_slist_move_tail(&sr_list, &smb_nce_list)) { 608 sr = list_head(&sr_list); 609 while (sr) { 610 ASSERT(sr->sr_magic == SMB_REQ_MAGIC); 611 tmp = list_next(&sr_list, sr); 612 list_remove(&sr_list, sr); 613 smb_reply_notify_change_request(sr); 614 sr = tmp; 615 } 616 } 617 } 618 list_destroy(&sr_list); 619 } 620