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
smb_fem_init(void)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
smb_fem_fini(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
smb_fem_fcn_install(smb_node_t * node)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
smb_fem_fcn_uninstall(smb_node_t * node)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
smb_fem_oplock_install(smb_node_t * node)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
smb_fem_oplock_uninstall(smb_node_t * node)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
smb_fem_fcn_create(femarg_t * arg,char * name,vattr_t * vap,vcexcl_t excl,int mode,vnode_t ** vpp,cred_t * cr,int flag,caller_context_t * ct,vsecattr_t * vsecp)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
smb_fem_fcn_remove(femarg_t * arg,char * name,cred_t * cr,caller_context_t * ct,int flags)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
smb_fem_fcn_rename(femarg_t * arg,char * snm,vnode_t * tdvp,char * tnm,cred_t * cr,caller_context_t * ct,int flags)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
smb_fem_fcn_mkdir(femarg_t * arg,char * name,vattr_t * vap,vnode_t ** vpp,cred_t * cr,caller_context_t * ct,int flags,vsecattr_t * vsecp)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
smb_fem_fcn_rmdir(femarg_t * arg,char * name,vnode_t * cdir,cred_t * cr,caller_context_t * ct,int flags)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
smb_fem_fcn_link(femarg_t * arg,vnode_t * svp,char * tnm,cred_t * cr,caller_context_t * ct,int flags)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
smb_fem_fcn_symlink(femarg_t * arg,char * linkname,vattr_t * vap,char * target,cred_t * cr,caller_context_t * ct,int flags)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
smb_fem_oplock_open(femarg_t * arg,int mode,cred_t * cr,caller_context_t * ct)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
smb_fem_oplock_read(femarg_t * arg,uio_t * uiop,int ioflag,cred_t * cr,caller_context_t * ct)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
smb_fem_oplock_write(femarg_t * arg,uio_t * uiop,int ioflag,cred_t * cr,caller_context_t * ct)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
smb_fem_oplock_setattr(femarg_t * arg,vattr_t * vap,int flags,cred_t * cr,caller_context_t * ct)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
smb_fem_oplock_space(femarg_t * arg,int cmd,flock64_t * bfp,int flag,offset_t offset,cred_t * cr,caller_context_t * ct)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
smb_fem_oplock_vnevent(femarg_t * arg,vnevent_t vnevent,vnode_t * dvp,char * name,caller_context_t * ct)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
smb_fem_oplock_wait(smb_node_t * node,caller_context_t * ct)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