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