xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb_fem.c (revision 069e6b7e31ba5dcbc5441b98af272714d9a5455c)
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 2017 Nexenta Systems, 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 void
174 smb_fem_fcn_uninstall(smb_node_t *node)
175 {
176 	if (smb_fcn_ops == NULL)
177 		return;
178 	VERIFY0(fem_uninstall(node->vp, smb_fcn_ops, (void *)node));
179 }
180 
181 int
182 smb_fem_oplock_install(smb_node_t *node)
183 {
184 	int rc;
185 
186 	if (smb_oplock_ops == NULL)
187 		return (ENOSYS);
188 	rc = fem_install(node->vp, smb_oplock_ops, (void *)node, OPARGUNIQ,
189 	    (fem_func_t)smb_node_ref, (fem_func_t)smb_node_release);
190 	return (rc);
191 }
192 
193 void
194 smb_fem_oplock_uninstall(smb_node_t *node)
195 {
196 	if (smb_oplock_ops == NULL)
197 		return;
198 	VERIFY0(fem_uninstall(node->vp, smb_oplock_ops, (void *)node));
199 }
200 
201 /*
202  * FEM FCN monitors
203  *
204  * The FCN monitors intercept the respective VOP_* call regardless
205  * of whether the call originates from CIFS, NFS, or a local process.
206  */
207 
208 /*
209  * smb_fem_fcn_create()
210  *
211  * This monitor will catch only changes to VREG files and not to extended
212  * attribute files.  This is fine because, for CIFS files, stream creates
213  * should not trigger any file change notification on the VDIR directory
214  * being monitored.  Creates of any other kind of extended attribute in
215  * the directory will also not trigger any file change notification on the
216  * VDIR directory being monitored.
217  */
218 
219 static int
220 smb_fem_fcn_create(
221     femarg_t *arg,
222     char *name,
223     vattr_t *vap,
224     vcexcl_t excl,
225     int mode,
226     vnode_t **vpp,
227     cred_t *cr,
228     int flag,
229     caller_context_t *ct,
230     vsecattr_t *vsecp)
231 {
232 	smb_node_t *dnode;
233 	int error;
234 
235 	dnode = (smb_node_t *)arg->fa_fnode->fn_available;
236 
237 	ASSERT(dnode);
238 
239 	error = vnext_create(arg, name, vap, excl, mode, vpp, cr, flag,
240 	    ct, vsecp);
241 
242 	if (error == 0 && ct != &smb_ct)
243 		smb_node_notify_change(dnode, FILE_ACTION_ADDED, name);
244 
245 	return (error);
246 }
247 
248 /*
249  * smb_fem_fcn_remove()
250  *
251  * This monitor will catch only changes to VREG files and to not extended
252  * attribute files.  This is fine because, for CIFS files, stream deletes
253  * should not trigger any file change notification on the VDIR directory
254  * being monitored.  Deletes of any other kind of extended attribute in
255  * the directory will also not trigger any file change notification on the
256  * VDIR directory being monitored.
257  */
258 
259 static int
260 smb_fem_fcn_remove(
261     femarg_t *arg,
262     char *name,
263     cred_t *cr,
264     caller_context_t *ct,
265     int flags)
266 {
267 	smb_node_t *dnode;
268 	int error;
269 
270 	dnode = (smb_node_t *)arg->fa_fnode->fn_available;
271 
272 	ASSERT(dnode);
273 
274 	error = vnext_remove(arg, name, cr, ct, flags);
275 
276 	if (error == 0 && ct != &smb_ct)
277 		smb_node_notify_change(dnode, FILE_ACTION_REMOVED, name);
278 
279 	return (error);
280 }
281 
282 static int
283 smb_fem_fcn_rename(
284     femarg_t *arg,
285     char *snm,
286     vnode_t *tdvp,
287     char *tnm,
288     cred_t *cr,
289     caller_context_t *ct,
290     int flags)
291 {
292 	smb_node_t *dnode;
293 	int error;
294 
295 	dnode = (smb_node_t *)arg->fa_fnode->fn_available;
296 
297 	ASSERT(dnode);
298 
299 	error = vnext_rename(arg, snm, tdvp, tnm, cr, ct, flags);
300 
301 	if (error == 0 && ct != &smb_ct) {
302 		/*
303 		 * Note that renames in the same directory are normally
304 		 * delivered in {old,new} pairs, and clients expect them
305 		 * in that order, if both events are delivered.
306 		 */
307 		smb_node_notify_change(dnode,
308 		    FILE_ACTION_RENAMED_OLD_NAME, snm);
309 		smb_node_notify_change(dnode,
310 		    FILE_ACTION_RENAMED_NEW_NAME, tnm);
311 	}
312 
313 	return (error);
314 }
315 
316 static int
317 smb_fem_fcn_mkdir(
318     femarg_t *arg,
319     char *name,
320     vattr_t *vap,
321     vnode_t **vpp,
322     cred_t *cr,
323     caller_context_t *ct,
324     int flags,
325     vsecattr_t *vsecp)
326 {
327 	smb_node_t *dnode;
328 	int error;
329 
330 	dnode = (smb_node_t *)arg->fa_fnode->fn_available;
331 
332 	ASSERT(dnode);
333 
334 	error = vnext_mkdir(arg, name, vap, vpp, cr, ct, flags, vsecp);
335 
336 	if (error == 0 && ct != &smb_ct)
337 		smb_node_notify_change(dnode, FILE_ACTION_ADDED, name);
338 
339 	return (error);
340 }
341 
342 static int
343 smb_fem_fcn_rmdir(
344     femarg_t *arg,
345     char *name,
346     vnode_t *cdir,
347     cred_t *cr,
348     caller_context_t *ct,
349     int flags)
350 {
351 	smb_node_t *dnode;
352 	int error;
353 
354 	dnode = (smb_node_t *)arg->fa_fnode->fn_available;
355 
356 	ASSERT(dnode);
357 
358 	error = vnext_rmdir(arg, name, cdir, cr, ct, flags);
359 
360 	if (error == 0 && ct != &smb_ct)
361 		smb_node_notify_change(dnode, FILE_ACTION_REMOVED, name);
362 
363 	return (error);
364 }
365 
366 static int
367 smb_fem_fcn_link(
368     femarg_t *arg,
369     vnode_t *svp,
370     char *tnm,
371     cred_t *cr,
372     caller_context_t *ct,
373     int flags)
374 {
375 	smb_node_t *dnode;
376 	int error;
377 
378 	dnode = (smb_node_t *)arg->fa_fnode->fn_available;
379 
380 	ASSERT(dnode);
381 
382 	error = vnext_link(arg, svp, tnm, cr, ct, flags);
383 
384 	if (error == 0 && ct != &smb_ct)
385 		smb_node_notify_change(dnode, FILE_ACTION_ADDED, tnm);
386 
387 	return (error);
388 }
389 
390 static int
391 smb_fem_fcn_symlink(
392     femarg_t *arg,
393     char *linkname,
394     vattr_t *vap,
395     char *target,
396     cred_t *cr,
397     caller_context_t *ct,
398     int flags)
399 {
400 	smb_node_t *dnode;
401 	int error;
402 
403 	dnode = (smb_node_t *)arg->fa_fnode->fn_available;
404 
405 	ASSERT(dnode);
406 
407 	error = vnext_symlink(arg, linkname, vap, target, cr, ct, flags);
408 
409 	if (error == 0 && ct != &smb_ct)
410 		smb_node_notify_change(dnode, FILE_ACTION_ADDED, linkname);
411 
412 	return (error);
413 }
414 
415 /*
416  * FEM oplock monitors
417  *
418  * The monitors below are not intended to intercept CIFS calls.
419  * CIFS higher-level routines will break oplocks as needed prior
420  * to getting to the VFS layer.
421  */
422 static int
423 smb_fem_oplock_open(
424     femarg_t		*arg,
425     int			mode,
426     cred_t		*cr,
427     caller_context_t	*ct)
428 {
429 	smb_node_t	*node;
430 	uint32_t	status;
431 	int		rc = 0;
432 
433 	if (ct != &smb_ct) {
434 		uint32_t req_acc = FILE_READ_DATA;
435 		uint32_t cr_disp = FILE_OPEN_IF;
436 
437 		node = (smb_node_t *)(arg->fa_fnode->fn_available);
438 		SMB_NODE_VALID(node);
439 
440 		/*
441 		 * Get req_acc, cr_disp just accurate enough so
442 		 * the oplock break call does the right thing.
443 		 */
444 		if (mode & FWRITE) {
445 			req_acc = FILE_READ_DATA | FILE_WRITE_DATA;
446 			cr_disp = (mode & FTRUNC) ?
447 			    FILE_OVERWRITE_IF : FILE_OPEN_IF;
448 		} else {
449 			req_acc = FILE_READ_DATA;
450 			cr_disp = FILE_OPEN_IF;
451 		}
452 
453 		status = smb_oplock_break_OPEN(node, NULL,
454 		    req_acc, cr_disp);
455 		if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS)
456 			rc = smb_fem_oplock_wait(node, ct);
457 		else if (status != 0)
458 			rc = EIO;
459 	}
460 	if (rc == 0)
461 		rc = vnext_open(arg, mode, cr, ct);
462 
463 	return (rc);
464 }
465 
466 /*
467  * Should normally be hit only via NFSv2/v3.  All other accesses
468  * (CIFS/NFS/local) should call VOP_OPEN first.
469  */
470 
471 static int
472 smb_fem_oplock_read(
473     femarg_t		*arg,
474     uio_t		*uiop,
475     int			ioflag,
476     cred_t		*cr,
477     caller_context_t	*ct)
478 {
479 	smb_node_t	*node;
480 	uint32_t	status;
481 	int	rc = 0;
482 
483 	if (ct != &smb_ct) {
484 		node = (smb_node_t *)(arg->fa_fnode->fn_available);
485 		SMB_NODE_VALID(node);
486 
487 		status = smb_oplock_break_READ(node, NULL);
488 		if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS)
489 			rc = smb_fem_oplock_wait(node, ct);
490 		else if (status != 0)
491 			rc = EIO;
492 	}
493 	if (rc == 0)
494 		rc = vnext_read(arg, uiop, ioflag, cr, ct);
495 
496 	return (rc);
497 }
498 
499 /*
500  * Should normally be hit only via NFSv2/v3.  All other accesses
501  * (CIFS/NFS/local) should call VOP_OPEN first.
502  */
503 
504 static int
505 smb_fem_oplock_write(
506     femarg_t		*arg,
507     uio_t		*uiop,
508     int			ioflag,
509     cred_t		*cr,
510     caller_context_t	*ct)
511 {
512 	smb_node_t	*node;
513 	uint32_t	status;
514 	int	rc = 0;
515 
516 	if (ct != &smb_ct) {
517 		node = (smb_node_t *)(arg->fa_fnode->fn_available);
518 		SMB_NODE_VALID(node);
519 
520 		status = smb_oplock_break_WRITE(node, NULL);
521 		if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS)
522 			rc = smb_fem_oplock_wait(node, ct);
523 		else if (status != 0)
524 			rc = EIO;
525 	}
526 	if (rc == 0)
527 		rc = vnext_write(arg, uiop, ioflag, cr, ct);
528 
529 	return (rc);
530 }
531 
532 static int
533 smb_fem_oplock_setattr(
534     femarg_t		*arg,
535     vattr_t		*vap,
536     int			flags,
537     cred_t		*cr,
538     caller_context_t	*ct)
539 {
540 	smb_node_t	*node;
541 	uint32_t	status;
542 	int	rc = 0;
543 
544 	if (ct != &smb_ct && (vap->va_mask & AT_SIZE) != 0) {
545 		node = (smb_node_t *)(arg->fa_fnode->fn_available);
546 		SMB_NODE_VALID(node);
547 
548 		status = smb_oplock_break_SETINFO(node, NULL,
549 		    FileEndOfFileInformation);
550 		if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS)
551 			rc = smb_fem_oplock_wait(node, ct);
552 		else if (status != 0)
553 			rc = EIO;
554 	}
555 	if (rc == 0)
556 		rc = vnext_setattr(arg, vap, flags, cr, ct);
557 	return (rc);
558 }
559 
560 static int
561 smb_fem_oplock_space(
562     femarg_t		*arg,
563     int			cmd,
564     flock64_t		*bfp,
565     int			flag,
566     offset_t		offset,
567     cred_t		*cr,
568     caller_context_t	*ct)
569 {
570 	smb_node_t	*node;
571 	uint32_t	status;
572 	int	rc = 0;
573 
574 	if (ct != &smb_ct) {
575 		node = (smb_node_t *)(arg->fa_fnode->fn_available);
576 		SMB_NODE_VALID(node);
577 
578 		status = smb_oplock_break_SETINFO(node, NULL,
579 		    FileAllocationInformation);
580 		if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS)
581 			rc = smb_fem_oplock_wait(node, ct);
582 		else if (status != 0)
583 			rc = EIO;
584 	}
585 	if (rc == 0)
586 		rc = vnext_space(arg, cmd, bfp, flag, offset, cr, ct);
587 	return (rc);
588 }
589 
590 /*
591  * smb_fem_oplock_vnevent()
592  *
593  * To intercept NFS and local renames and removes in order to break any
594  * existing oplock prior to the operation.
595  *
596  * Note: Currently, this monitor is traversed only when an FS is mounted
597  * non-nbmand.  (When the FS is mounted nbmand, share reservation checking
598  * will detect a share violation and return an error prior to the VOP layer
599  * being reached.)  Thus, for nbmand NFS and local renames and removes,
600  * an existing oplock is never broken prior to share checking (contrary to
601  * how it is with intra-CIFS remove and rename requests).
602  */
603 
604 static int
605 smb_fem_oplock_vnevent(
606     femarg_t		*arg,
607     vnevent_t		vnevent,
608     vnode_t		*dvp,
609     char		*name,
610     caller_context_t	*ct)
611 {
612 	smb_node_t	*node;
613 	uint32_t	status;
614 	int		rc = 0;
615 
616 	if (ct != &smb_ct) {
617 		node = (smb_node_t *)(arg->fa_fnode->fn_available);
618 		SMB_NODE_VALID(node);
619 
620 		switch (vnevent) {
621 		case VE_REMOVE:
622 		case VE_PRE_RENAME_DEST:
623 		case VE_RENAME_DEST:
624 			status = smb_oplock_break_HANDLE(node, NULL);
625 			break;
626 		case VE_PRE_RENAME_SRC:
627 		case VE_RENAME_SRC:
628 			status = smb_oplock_break_SETINFO(node, NULL,
629 			    FileRenameInformation);
630 			break;
631 		default:
632 			status = 0;
633 			break;
634 		}
635 		if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS)
636 			rc = smb_fem_oplock_wait(node, ct);
637 		else if (status != 0)
638 			rc = EIO;
639 	}
640 	if (rc == 0)
641 		rc = vnext_vnevent(arg, vnevent, dvp, name, ct);
642 
643 	return (rc);
644 }
645 
646 int smb_fem_oplock_timeout = 5000; /* mSec. */
647 
648 static int
649 smb_fem_oplock_wait(smb_node_t *node, caller_context_t *ct)
650 {
651 	int		rc = 0;
652 
653 	ASSERT(ct != &smb_ct);
654 
655 	if (ct && (ct->cc_flags & CC_DONTBLOCK)) {
656 		ct->cc_flags |= CC_WOULDBLOCK;
657 		rc = EAGAIN;
658 	} else {
659 		(void) smb_oplock_wait_break(node,
660 		    smb_fem_oplock_timeout);
661 		rc = 0;
662 	}
663 
664 	return (rc);
665 }
666