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 (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. 23 * Copyright 2020 Tintri by DDN, Inc. All rights reserved. 24 * Copyright 2015 Joyent, Inc. 25 * Copyright 2022 RackTop Systems, Inc. 26 */ 27 28 #include <smbsrv/smb_kproto.h> 29 #include <smbsrv/smb_fsops.h> 30 #include <sys/sdt.h> 31 #include <sys/fcntl.h> 32 #include <sys/vfs.h> 33 #include <sys/vfs_opreg.h> 34 #include <sys/vnode.h> 35 #include <sys/fem.h> 36 37 extern caller_context_t smb_ct; 38 39 static boolean_t smb_fem_initialized = B_FALSE; 40 static fem_t *smb_fcn_ops = NULL; 41 static fem_t *smb_oplock_ops = NULL; 42 43 /* 44 * Declarations for FCN (file change notification) FEM monitors 45 */ 46 47 static int smb_fem_fcn_create(femarg_t *, char *, vattr_t *, vcexcl_t, int, 48 vnode_t **, cred_t *, int, caller_context_t *, vsecattr_t *); 49 static int smb_fem_fcn_remove(femarg_t *, char *, cred_t *, 50 caller_context_t *, int); 51 static int smb_fem_fcn_rename(femarg_t *, char *, vnode_t *, char *, 52 cred_t *, caller_context_t *, int); 53 static int smb_fem_fcn_mkdir(femarg_t *, char *, vattr_t *, vnode_t **, 54 cred_t *, caller_context_t *, int, vsecattr_t *); 55 static int smb_fem_fcn_rmdir(femarg_t *, char *, vnode_t *, cred_t *, 56 caller_context_t *, int); 57 static int smb_fem_fcn_link(femarg_t *, vnode_t *, char *, cred_t *, 58 caller_context_t *, int); 59 static int smb_fem_fcn_symlink(femarg_t *, char *, vattr_t *, 60 char *, cred_t *, caller_context_t *, int); 61 62 static const fs_operation_def_t smb_fcn_tmpl[] = { 63 VOPNAME_CREATE, { .femop_create = smb_fem_fcn_create }, 64 VOPNAME_REMOVE, {.femop_remove = smb_fem_fcn_remove}, 65 VOPNAME_RENAME, {.femop_rename = smb_fem_fcn_rename}, 66 VOPNAME_MKDIR, {.femop_mkdir = smb_fem_fcn_mkdir}, 67 VOPNAME_RMDIR, {.femop_rmdir = smb_fem_fcn_rmdir}, 68 VOPNAME_LINK, {.femop_link = smb_fem_fcn_link}, 69 VOPNAME_SYMLINK, {.femop_symlink = smb_fem_fcn_symlink}, 70 NULL, NULL 71 }; 72 73 /* 74 * Declarations for oplock FEM monitors 75 */ 76 77 static int smb_fem_oplock_open(femarg_t *, int, cred_t *, 78 struct caller_context *); 79 static int smb_fem_oplock_read(femarg_t *, uio_t *, int, cred_t *, 80 struct caller_context *); 81 static int smb_fem_oplock_write(femarg_t *, uio_t *, int, cred_t *, 82 struct caller_context *); 83 static int smb_fem_oplock_setattr(femarg_t *, vattr_t *, int, cred_t *, 84 caller_context_t *); 85 static int smb_fem_oplock_space(femarg_t *, int, flock64_t *, int, 86 offset_t, cred_t *, caller_context_t *); 87 static int smb_fem_oplock_vnevent(femarg_t *, vnevent_t, vnode_t *, char *, 88 caller_context_t *); 89 90 static const fs_operation_def_t smb_oplock_tmpl[] = { 91 VOPNAME_OPEN, { .femop_open = smb_fem_oplock_open }, 92 VOPNAME_READ, { .femop_read = smb_fem_oplock_read }, 93 VOPNAME_WRITE, { .femop_write = smb_fem_oplock_write }, 94 VOPNAME_SETATTR, { .femop_setattr = smb_fem_oplock_setattr }, 95 VOPNAME_SPACE, { .femop_space = smb_fem_oplock_space }, 96 VOPNAME_VNEVENT, { .femop_vnevent = smb_fem_oplock_vnevent }, 97 NULL, NULL 98 }; 99 100 static int smb_fem_oplock_wait(smb_node_t *, caller_context_t *); 101 102 /* 103 * smb_fem_init 104 * 105 * This function is not multi-thread safe. The caller must make sure only one 106 * thread makes the call. 107 */ 108 int 109 smb_fem_init(void) 110 { 111 int rc = 0; 112 113 if (smb_fem_initialized) 114 return (0); 115 116 rc = fem_create("smb_fcn_ops", smb_fcn_tmpl, &smb_fcn_ops); 117 if (rc) 118 return (rc); 119 120 rc = fem_create("smb_oplock_ops", smb_oplock_tmpl, 121 &smb_oplock_ops); 122 123 if (rc) { 124 fem_free(smb_fcn_ops); 125 smb_fcn_ops = NULL; 126 return (rc); 127 } 128 129 smb_fem_initialized = B_TRUE; 130 131 return (0); 132 } 133 134 /* 135 * smb_fem_fini 136 * 137 * This function is not multi-thread safe. The caller must make sure only one 138 * thread makes the call. 139 */ 140 void 141 smb_fem_fini(void) 142 { 143 if (!smb_fem_initialized) 144 return; 145 146 if (smb_fcn_ops != NULL) { 147 fem_free(smb_fcn_ops); 148 smb_fcn_ops = NULL; 149 } 150 if (smb_oplock_ops != NULL) { 151 fem_free(smb_oplock_ops); 152 smb_oplock_ops = NULL; 153 } 154 smb_fem_initialized = B_FALSE; 155 } 156 157 /* 158 * Install our fem hooks for change notify. 159 * Not using hold/rele function here because we 160 * remove the fem hooks before node destroy. 161 */ 162 int 163 smb_fem_fcn_install(smb_node_t *node) 164 { 165 int rc; 166 167 if (smb_fcn_ops == NULL) 168 return (ENOSYS); 169 rc = fem_install(node->vp, smb_fcn_ops, (void *)node, OPARGUNIQ, 170 NULL, NULL); 171 return (rc); 172 } 173 174 int 175 smb_fem_fcn_uninstall(smb_node_t *node) 176 { 177 int rc; 178 179 if (smb_fcn_ops == NULL) 180 return (ENOSYS); 181 rc = fem_uninstall(node->vp, smb_fcn_ops, (void *)node); 182 return (rc); 183 } 184 185 int 186 smb_fem_oplock_install(smb_node_t *node) 187 { 188 int rc; 189 190 if (smb_oplock_ops == NULL) 191 return (ENOSYS); 192 rc = fem_install(node->vp, smb_oplock_ops, (void *)node, OPARGUNIQ, 193 (fem_func_t)smb_node_ref, (fem_func_t)smb_node_release); 194 return (rc); 195 } 196 197 void 198 smb_fem_oplock_uninstall(smb_node_t *node) 199 { 200 if (smb_oplock_ops == NULL) 201 return; 202 VERIFY0(fem_uninstall(node->vp, smb_oplock_ops, (void *)node)); 203 } 204 205 /* 206 * FEM FCN monitors 207 * 208 * The FCN monitors intercept the respective VOP_* call regardless 209 * of whether the call originates from CIFS, NFS, or a local process. 210 * 211 * Here we're only interested in operations that change the list of 212 * names contained in the directory. SMB clients can also ask to be 213 * notified about events where a file in this directory has had its 214 * meta-data changed (size, times, etc) but that's outside of the 215 * design intent for these FEM hooks. Those meta-data events DO 216 * happen when caused by SMB clients (via smb_node_notify_modified) 217 * but not by other FS activity because we don't have a good way to 218 * place all the FEM hooks that would be required for that, and if 219 * we did, the performance cost could be severe. 220 */ 221 222 /* 223 * smb_fem_fcn_create() 224 * 225 * This monitor will catch only changes to VREG files and not to extended 226 * attribute files. This is fine because, for CIFS files, stream creates 227 * should not trigger any file change notification on the VDIR directory 228 * being monitored. Creates of any other kind of extended attribute in 229 * the directory will also not trigger any file change notification on the 230 * VDIR directory being monitored. 231 */ 232 233 static int 234 smb_fem_fcn_create( 235 femarg_t *arg, 236 char *name, 237 vattr_t *vap, 238 vcexcl_t excl, 239 int mode, 240 vnode_t **vpp, 241 cred_t *cr, 242 int flag, 243 caller_context_t *ct, 244 vsecattr_t *vsecp) 245 { 246 smb_node_t *dnode; 247 int error; 248 249 dnode = (smb_node_t *)arg->fa_fnode->fn_available; 250 251 ASSERT(dnode); 252 253 error = vnext_create(arg, name, vap, excl, mode, vpp, cr, flag, 254 ct, vsecp); 255 256 if (error == 0 && ct != &smb_ct) 257 smb_node_notify_change(dnode, FILE_ACTION_ADDED, name); 258 259 return (error); 260 } 261 262 /* 263 * smb_fem_fcn_remove() 264 * 265 * This monitor will catch only changes to VREG files and to not extended 266 * attribute files. This is fine because, for CIFS files, stream deletes 267 * should not trigger any file change notification on the VDIR directory 268 * being monitored. Deletes of any other kind of extended attribute in 269 * the directory will also not trigger any file change notification on the 270 * VDIR directory being monitored. 271 */ 272 273 static int 274 smb_fem_fcn_remove( 275 femarg_t *arg, 276 char *name, 277 cred_t *cr, 278 caller_context_t *ct, 279 int flags) 280 { 281 smb_node_t *dnode; 282 int error; 283 284 dnode = (smb_node_t *)arg->fa_fnode->fn_available; 285 286 ASSERT(dnode); 287 288 error = vnext_remove(arg, name, cr, ct, flags); 289 290 if (error == 0 && ct != &smb_ct) 291 smb_node_notify_change(dnode, FILE_ACTION_REMOVED, name); 292 293 return (error); 294 } 295 296 static int 297 smb_fem_fcn_rename( 298 femarg_t *arg, 299 char *snm, 300 vnode_t *tdvp, 301 char *tnm, 302 cred_t *cr, 303 caller_context_t *ct, 304 int flags) 305 { 306 smb_node_t *dnode; 307 int error; 308 309 dnode = (smb_node_t *)arg->fa_fnode->fn_available; 310 311 ASSERT(dnode); 312 313 error = vnext_rename(arg, snm, tdvp, tnm, cr, ct, flags); 314 315 if (error == 0 && ct != &smb_ct) { 316 /* 317 * Note that renames in the same directory are normally 318 * delivered in {old,new} pairs, and clients expect them 319 * in that order, if both events are delivered. 320 */ 321 smb_node_notify_change(dnode, 322 FILE_ACTION_RENAMED_OLD_NAME, snm); 323 smb_node_notify_change(dnode, 324 FILE_ACTION_RENAMED_NEW_NAME, tnm); 325 } 326 327 return (error); 328 } 329 330 static int 331 smb_fem_fcn_mkdir( 332 femarg_t *arg, 333 char *name, 334 vattr_t *vap, 335 vnode_t **vpp, 336 cred_t *cr, 337 caller_context_t *ct, 338 int flags, 339 vsecattr_t *vsecp) 340 { 341 smb_node_t *dnode; 342 int error; 343 344 dnode = (smb_node_t *)arg->fa_fnode->fn_available; 345 346 ASSERT(dnode); 347 348 error = vnext_mkdir(arg, name, vap, vpp, cr, ct, flags, vsecp); 349 350 if (error == 0 && ct != &smb_ct) 351 smb_node_notify_change(dnode, FILE_ACTION_ADDED, name); 352 353 return (error); 354 } 355 356 static int 357 smb_fem_fcn_rmdir( 358 femarg_t *arg, 359 char *name, 360 vnode_t *cdir, 361 cred_t *cr, 362 caller_context_t *ct, 363 int flags) 364 { 365 smb_node_t *dnode; 366 int error; 367 368 dnode = (smb_node_t *)arg->fa_fnode->fn_available; 369 370 ASSERT(dnode); 371 372 error = vnext_rmdir(arg, name, cdir, cr, ct, flags); 373 374 if (error == 0 && ct != &smb_ct) 375 smb_node_notify_change(dnode, FILE_ACTION_REMOVED, name); 376 377 return (error); 378 } 379 380 static int 381 smb_fem_fcn_link( 382 femarg_t *arg, 383 vnode_t *svp, 384 char *tnm, 385 cred_t *cr, 386 caller_context_t *ct, 387 int flags) 388 { 389 smb_node_t *dnode; 390 int error; 391 392 dnode = (smb_node_t *)arg->fa_fnode->fn_available; 393 394 ASSERT(dnode); 395 396 error = vnext_link(arg, svp, tnm, cr, ct, flags); 397 398 if (error == 0 && ct != &smb_ct) 399 smb_node_notify_change(dnode, FILE_ACTION_ADDED, tnm); 400 401 return (error); 402 } 403 404 static int 405 smb_fem_fcn_symlink( 406 femarg_t *arg, 407 char *linkname, 408 vattr_t *vap, 409 char *target, 410 cred_t *cr, 411 caller_context_t *ct, 412 int flags) 413 { 414 smb_node_t *dnode; 415 int error; 416 417 dnode = (smb_node_t *)arg->fa_fnode->fn_available; 418 419 ASSERT(dnode); 420 421 error = vnext_symlink(arg, linkname, vap, target, cr, ct, flags); 422 423 if (error == 0 && ct != &smb_ct) 424 smb_node_notify_change(dnode, FILE_ACTION_ADDED, linkname); 425 426 return (error); 427 } 428 429 /* 430 * FEM oplock monitors 431 * 432 * The monitors below are not intended to intercept CIFS calls. 433 * CIFS higher-level routines will break oplocks as needed prior 434 * to getting to the VFS layer. 435 */ 436 static int 437 smb_fem_oplock_open( 438 femarg_t *arg, 439 int mode, 440 cred_t *cr, 441 caller_context_t *ct) 442 { 443 smb_node_t *node; 444 uint32_t status; 445 int rc = 0; 446 447 if (ct != &smb_ct) { 448 uint32_t req_acc = FILE_READ_DATA; 449 uint32_t cr_disp = FILE_OPEN_IF; 450 451 node = (smb_node_t *)(arg->fa_fnode->fn_available); 452 SMB_NODE_VALID(node); 453 454 /* 455 * Get req_acc, cr_disp just accurate enough so 456 * the oplock break call does the right thing. 457 */ 458 if (mode & FWRITE) { 459 req_acc = FILE_READ_DATA | FILE_WRITE_DATA; 460 cr_disp = (mode & FTRUNC) ? 461 FILE_OVERWRITE_IF : FILE_OPEN_IF; 462 } else { 463 req_acc = FILE_READ_DATA; 464 cr_disp = FILE_OPEN_IF; 465 } 466 467 status = smb_oplock_break_OPEN(node, NULL, 468 req_acc, cr_disp); 469 if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) 470 rc = smb_fem_oplock_wait(node, ct); 471 else if (status != 0) 472 rc = EIO; 473 } 474 if (rc == 0) 475 rc = vnext_open(arg, mode, cr, ct); 476 477 return (rc); 478 } 479 480 /* 481 * Should normally be hit only via NFSv2/v3. All other accesses 482 * (CIFS/NFS/local) should call VOP_OPEN first. 483 */ 484 485 static int 486 smb_fem_oplock_read( 487 femarg_t *arg, 488 uio_t *uiop, 489 int ioflag, 490 cred_t *cr, 491 caller_context_t *ct) 492 { 493 smb_node_t *node; 494 uint32_t status; 495 int rc = 0; 496 497 if (ct != &smb_ct) { 498 node = (smb_node_t *)(arg->fa_fnode->fn_available); 499 SMB_NODE_VALID(node); 500 501 status = smb_oplock_break_READ(node, NULL); 502 if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) 503 rc = smb_fem_oplock_wait(node, ct); 504 else if (status != 0) 505 rc = EIO; 506 } 507 if (rc == 0) 508 rc = vnext_read(arg, uiop, ioflag, cr, ct); 509 510 return (rc); 511 } 512 513 /* 514 * Should normally be hit only via NFSv2/v3. All other accesses 515 * (CIFS/NFS/local) should call VOP_OPEN first. 516 */ 517 518 static int 519 smb_fem_oplock_write( 520 femarg_t *arg, 521 uio_t *uiop, 522 int ioflag, 523 cred_t *cr, 524 caller_context_t *ct) 525 { 526 smb_node_t *node; 527 uint32_t status; 528 int rc = 0; 529 530 if (ct != &smb_ct) { 531 node = (smb_node_t *)(arg->fa_fnode->fn_available); 532 SMB_NODE_VALID(node); 533 534 status = smb_oplock_break_WRITE(node, NULL); 535 if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) 536 rc = smb_fem_oplock_wait(node, ct); 537 else if (status != 0) 538 rc = EIO; 539 } 540 if (rc == 0) 541 rc = vnext_write(arg, uiop, ioflag, cr, ct); 542 543 return (rc); 544 } 545 546 static int 547 smb_fem_oplock_setattr( 548 femarg_t *arg, 549 vattr_t *vap, 550 int flags, 551 cred_t *cr, 552 caller_context_t *ct) 553 { 554 smb_node_t *node; 555 uint32_t status; 556 int rc = 0; 557 558 if (ct != &smb_ct && (vap->va_mask & AT_SIZE) != 0) { 559 node = (smb_node_t *)(arg->fa_fnode->fn_available); 560 SMB_NODE_VALID(node); 561 562 status = smb_oplock_break_SETINFO(node, NULL, 563 FileEndOfFileInformation); 564 if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) 565 rc = smb_fem_oplock_wait(node, ct); 566 else if (status != 0) 567 rc = EIO; 568 } 569 if (rc == 0) 570 rc = vnext_setattr(arg, vap, flags, cr, ct); 571 return (rc); 572 } 573 574 static int 575 smb_fem_oplock_space( 576 femarg_t *arg, 577 int cmd, 578 flock64_t *bfp, 579 int flag, 580 offset_t offset, 581 cred_t *cr, 582 caller_context_t *ct) 583 { 584 smb_node_t *node; 585 uint32_t status; 586 int rc = 0; 587 588 if (ct != &smb_ct) { 589 node = (smb_node_t *)(arg->fa_fnode->fn_available); 590 SMB_NODE_VALID(node); 591 592 status = smb_oplock_break_SETINFO(node, NULL, 593 FileAllocationInformation); 594 if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) 595 rc = smb_fem_oplock_wait(node, ct); 596 else if (status != 0) 597 rc = EIO; 598 } 599 if (rc == 0) 600 rc = vnext_space(arg, cmd, bfp, flag, offset, cr, ct); 601 return (rc); 602 } 603 604 /* 605 * smb_fem_oplock_vnevent() 606 * 607 * To intercept NFS and local renames and removes in order to break any 608 * existing oplock prior to the operation. 609 * 610 * Note: Currently, this monitor is traversed only when an FS is mounted 611 * non-nbmand. (When the FS is mounted nbmand, share reservation checking 612 * will detect a share violation and return an error prior to the VOP layer 613 * being reached.) Thus, for nbmand NFS and local renames and removes, 614 * an existing oplock is never broken prior to share checking (contrary to 615 * how it is with intra-CIFS remove and rename requests). 616 */ 617 618 static int 619 smb_fem_oplock_vnevent( 620 femarg_t *arg, 621 vnevent_t vnevent, 622 vnode_t *dvp, 623 char *name, 624 caller_context_t *ct) 625 { 626 smb_node_t *node; 627 uint32_t status; 628 int rc = 0; 629 630 if (ct != &smb_ct) { 631 node = (smb_node_t *)(arg->fa_fnode->fn_available); 632 SMB_NODE_VALID(node); 633 634 switch (vnevent) { 635 case VE_REMOVE: 636 case VE_PRE_RENAME_DEST: 637 case VE_RENAME_DEST: 638 status = smb_oplock_break_HANDLE(node, NULL); 639 break; 640 case VE_PRE_RENAME_SRC: 641 case VE_RENAME_SRC: 642 status = smb_oplock_break_SETINFO(node, NULL, 643 FileRenameInformation); 644 break; 645 default: 646 status = 0; 647 break; 648 } 649 if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) 650 rc = smb_fem_oplock_wait(node, ct); 651 else if (status != 0) 652 rc = EIO; 653 } 654 if (rc == 0) 655 rc = vnext_vnevent(arg, vnevent, dvp, name, ct); 656 657 return (rc); 658 } 659 660 int smb_fem_oplock_timeout = 5000; /* mSec. */ 661 662 static int 663 smb_fem_oplock_wait(smb_node_t *node, caller_context_t *ct) 664 { 665 int rc = 0; 666 667 ASSERT(ct != &smb_ct); 668 669 if (ct && (ct->cc_flags & CC_DONTBLOCK)) { 670 ct->cc_flags |= CC_WOULDBLOCK; 671 rc = EAGAIN; 672 } else { 673 (void) smb_oplock_wait_break_fem(node, 674 smb_fem_oplock_timeout); 675 rc = 0; 676 } 677 678 return (rc); 679 } 680