xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb_fem.c (revision 7a6d80f1660abd4755c68cbd094d4a914681d26e)
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