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