xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb_fem.c (revision 3c6b90be1d51de874ba4c1f05537c85375b6ab6e)
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 2013 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_rwlock(femarg_t *, int, 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_RWLOCK, { .femop_rwlock = smb_fem_oplock_rwlock },
96 	VOPNAME_SPACE,	{ .femop_space = smb_fem_oplock_space },
97 	VOPNAME_VNEVENT, { .femop_vnevent = smb_fem_oplock_vnevent },
98 	NULL, NULL
99 };
100 
101 static int smb_fem_oplock_break(femarg_t *, caller_context_t *, uint32_t);
102 
103 /*
104  * smb_fem_init
105  *
106  * This function is not multi-thread safe. The caller must make sure only one
107  * thread makes the call.
108  */
109 int
110 smb_fem_init(void)
111 {
112 	int	rc = 0;
113 
114 	if (smb_fem_initialized)
115 		return (0);
116 
117 	rc = fem_create("smb_fcn_ops", smb_fcn_tmpl, &smb_fcn_ops);
118 	if (rc)
119 		return (rc);
120 
121 	rc = fem_create("smb_oplock_ops", smb_oplock_tmpl,
122 	    &smb_oplock_ops);
123 
124 	if (rc) {
125 		fem_free(smb_fcn_ops);
126 		smb_fcn_ops = NULL;
127 		return (rc);
128 	}
129 
130 	smb_fem_initialized = B_TRUE;
131 
132 	return (0);
133 }
134 
135 /*
136  * smb_fem_fini
137  *
138  * This function is not multi-thread safe. The caller must make sure only one
139  * thread makes the call.
140  */
141 void
142 smb_fem_fini(void)
143 {
144 	if (!smb_fem_initialized)
145 		return;
146 
147 	if (smb_fcn_ops != NULL) {
148 		fem_free(smb_fcn_ops);
149 		smb_fcn_ops = NULL;
150 	}
151 	if (smb_oplock_ops != NULL) {
152 		fem_free(smb_oplock_ops);
153 		smb_oplock_ops = NULL;
154 	}
155 	smb_fem_initialized = B_FALSE;
156 }
157 
158 int
159 smb_fem_fcn_install(smb_node_t *node)
160 {
161 	int rc;
162 
163 	if (smb_fcn_ops == NULL)
164 		return (ENOSYS);
165 	rc = fem_install(node->vp, smb_fcn_ops, (void *)node, OPARGUNIQ,
166 	    (fem_func_t)smb_node_ref, (fem_func_t)smb_node_release);
167 	return (rc);
168 }
169 
170 void
171 smb_fem_fcn_uninstall(smb_node_t *node)
172 {
173 	if (smb_fcn_ops == NULL)
174 		return;
175 	VERIFY0(fem_uninstall(node->vp, smb_fcn_ops, (void *)node));
176 }
177 
178 int
179 smb_fem_oplock_install(smb_node_t *node)
180 {
181 	int rc;
182 
183 	if (smb_oplock_ops == NULL)
184 		return (ENOSYS);
185 	rc = fem_install(node->vp, smb_oplock_ops, (void *)node, OPARGUNIQ,
186 	    (fem_func_t)smb_node_ref, (fem_func_t)smb_node_release);
187 	return (rc);
188 }
189 
190 void
191 smb_fem_oplock_uninstall(smb_node_t *node)
192 {
193 	if (smb_oplock_ops == NULL)
194 		return;
195 	VERIFY0(fem_uninstall(node->vp, smb_oplock_ops, (void *)node));
196 }
197 
198 /*
199  * FEM FCN monitors
200  *
201  * The FCN monitors intercept the respective VOP_* call regardless
202  * of whether the call originates from CIFS, NFS, or a local process.
203  */
204 
205 /*
206  * smb_fem_fcn_create()
207  *
208  * This monitor will catch only changes to VREG files and not to extended
209  * attribute files.  This is fine because, for CIFS files, stream creates
210  * should not trigger any file change notification on the VDIR directory
211  * being monitored.  Creates of any other kind of extended attribute in
212  * the directory will also not trigger any file change notification on the
213  * VDIR directory being monitored.
214  */
215 
216 static int
217 smb_fem_fcn_create(
218     femarg_t *arg,
219     char *name,
220     vattr_t *vap,
221     vcexcl_t excl,
222     int mode,
223     vnode_t **vpp,
224     cred_t *cr,
225     int flag,
226     caller_context_t *ct,
227     vsecattr_t *vsecp)
228 {
229 	smb_node_t *dnode;
230 	int error;
231 
232 	dnode = (smb_node_t *)arg->fa_fnode->fn_available;
233 
234 	ASSERT(dnode);
235 
236 	error = vnext_create(arg, name, vap, excl, mode, vpp, cr, flag,
237 	    ct, vsecp);
238 
239 	if (error == 0 && ct != &smb_ct)
240 		smb_node_notify_change(dnode, FILE_ACTION_ADDED, name);
241 
242 	return (error);
243 }
244 
245 /*
246  * smb_fem_fcn_remove()
247  *
248  * This monitor will catch only changes to VREG files and to not extended
249  * attribute files.  This is fine because, for CIFS files, stream deletes
250  * should not trigger any file change notification on the VDIR directory
251  * being monitored.  Deletes of any other kind of extended attribute in
252  * the directory will also not trigger any file change notification on the
253  * VDIR directory being monitored.
254  */
255 
256 static int
257 smb_fem_fcn_remove(
258     femarg_t *arg,
259     char *name,
260     cred_t *cr,
261     caller_context_t *ct,
262     int flags)
263 {
264 	smb_node_t *dnode;
265 	int error;
266 
267 	dnode = (smb_node_t *)arg->fa_fnode->fn_available;
268 
269 	ASSERT(dnode);
270 
271 	error = vnext_remove(arg, name, cr, ct, flags);
272 
273 	if (error == 0 && ct != &smb_ct)
274 		smb_node_notify_change(dnode, FILE_ACTION_REMOVED, name);
275 
276 	return (error);
277 }
278 
279 static int
280 smb_fem_fcn_rename(
281     femarg_t *arg,
282     char *snm,
283     vnode_t *tdvp,
284     char *tnm,
285     cred_t *cr,
286     caller_context_t *ct,
287     int flags)
288 {
289 	smb_node_t *dnode;
290 	int error;
291 
292 	dnode = (smb_node_t *)arg->fa_fnode->fn_available;
293 
294 	ASSERT(dnode);
295 
296 	error = vnext_rename(arg, snm, tdvp, tnm, cr, ct, flags);
297 
298 	if (error == 0 && ct != &smb_ct) {
299 		/*
300 		 * Note that renames in the same directory are normally
301 		 * delivered in {old,new} pairs, and clients expect them
302 		 * in that order, if both events are delivered.
303 		 */
304 		smb_node_notify_change(dnode,
305 		    FILE_ACTION_RENAMED_OLD_NAME, snm);
306 		smb_node_notify_change(dnode,
307 		    FILE_ACTION_RENAMED_NEW_NAME, tnm);
308 	}
309 
310 	return (error);
311 }
312 
313 static int
314 smb_fem_fcn_mkdir(
315     femarg_t *arg,
316     char *name,
317     vattr_t *vap,
318     vnode_t **vpp,
319     cred_t *cr,
320     caller_context_t *ct,
321     int flags,
322     vsecattr_t *vsecp)
323 {
324 	smb_node_t *dnode;
325 	int error;
326 
327 	dnode = (smb_node_t *)arg->fa_fnode->fn_available;
328 
329 	ASSERT(dnode);
330 
331 	error = vnext_mkdir(arg, name, vap, vpp, cr, ct, flags, vsecp);
332 
333 	if (error == 0 && ct != &smb_ct)
334 		smb_node_notify_change(dnode, FILE_ACTION_ADDED, name);
335 
336 	return (error);
337 }
338 
339 static int
340 smb_fem_fcn_rmdir(
341     femarg_t *arg,
342     char *name,
343     vnode_t *cdir,
344     cred_t *cr,
345     caller_context_t *ct,
346     int flags)
347 {
348 	smb_node_t *dnode;
349 	int error;
350 
351 	dnode = (smb_node_t *)arg->fa_fnode->fn_available;
352 
353 	ASSERT(dnode);
354 
355 	error = vnext_rmdir(arg, name, cdir, cr, ct, flags);
356 
357 	if (error == 0 && ct != &smb_ct)
358 		smb_node_notify_change(dnode, FILE_ACTION_REMOVED, name);
359 
360 	return (error);
361 }
362 
363 static int
364 smb_fem_fcn_link(
365     femarg_t *arg,
366     vnode_t *svp,
367     char *tnm,
368     cred_t *cr,
369     caller_context_t *ct,
370     int flags)
371 {
372 	smb_node_t *dnode;
373 	int error;
374 
375 	dnode = (smb_node_t *)arg->fa_fnode->fn_available;
376 
377 	ASSERT(dnode);
378 
379 	error = vnext_link(arg, svp, tnm, cr, ct, flags);
380 
381 	if (error == 0 && ct != &smb_ct)
382 		smb_node_notify_change(dnode, FILE_ACTION_ADDED, tnm);
383 
384 	return (error);
385 }
386 
387 static int
388 smb_fem_fcn_symlink(
389     femarg_t *arg,
390     char *linkname,
391     vattr_t *vap,
392     char *target,
393     cred_t *cr,
394     caller_context_t *ct,
395     int flags)
396 {
397 	smb_node_t *dnode;
398 	int error;
399 
400 	dnode = (smb_node_t *)arg->fa_fnode->fn_available;
401 
402 	ASSERT(dnode);
403 
404 	error = vnext_symlink(arg, linkname, vap, target, cr, ct, flags);
405 
406 	if (error == 0 && ct != &smb_ct)
407 		smb_node_notify_change(dnode, FILE_ACTION_ADDED, linkname);
408 
409 	return (error);
410 }
411 
412 /*
413  * FEM oplock monitors
414  *
415  * The monitors below are not intended to intercept CIFS calls.
416  * CIFS higher-level routines will break oplocks as needed prior
417  * to getting to the VFS layer.
418  */
419 static int
420 smb_fem_oplock_open(
421     femarg_t		*arg,
422     int			mode,
423     cred_t		*cr,
424     caller_context_t	*ct)
425 {
426 	uint32_t	flags;
427 	int		rc = 0;
428 
429 	if (ct != &smb_ct) {
430 		if (mode & (FWRITE|FTRUNC))
431 			flags = SMB_OPLOCK_BREAK_TO_NONE;
432 		else
433 			flags = SMB_OPLOCK_BREAK_TO_LEVEL_II;
434 		rc = smb_fem_oplock_break(arg, ct, flags);
435 	}
436 	if (rc == 0)
437 		rc = vnext_open(arg, mode, cr, ct);
438 
439 	return (rc);
440 }
441 
442 /*
443  * Should normally be hit only via NFSv2/v3.  All other accesses
444  * (CIFS/NFS/local) should call VOP_OPEN first.
445  */
446 
447 static int
448 smb_fem_oplock_read(
449     femarg_t		*arg,
450     uio_t		*uiop,
451     int			ioflag,
452     cred_t		*cr,
453     caller_context_t	*ct)
454 {
455 	int	rc = 0;
456 
457 	if (ct != &smb_ct) {
458 		rc = smb_fem_oplock_break(arg, ct,
459 		    SMB_OPLOCK_BREAK_TO_LEVEL_II);
460 	}
461 	if (rc == 0)
462 		rc = vnext_read(arg, uiop, ioflag, cr, ct);
463 
464 	return (rc);
465 }
466 
467 /*
468  * Should normally be hit only via NFSv2/v3.  All other accesses
469  * (CIFS/NFS/local) should call VOP_OPEN first.
470  */
471 
472 static int
473 smb_fem_oplock_write(
474     femarg_t		*arg,
475     uio_t		*uiop,
476     int			ioflag,
477     cred_t		*cr,
478     caller_context_t	*ct)
479 {
480 	int	rc = 0;
481 
482 	if (ct != &smb_ct)
483 		rc = smb_fem_oplock_break(arg, ct, SMB_OPLOCK_BREAK_TO_NONE);
484 	if (rc == 0)
485 		rc = vnext_write(arg, uiop, ioflag, cr, ct);
486 
487 	return (rc);
488 }
489 
490 static int
491 smb_fem_oplock_setattr(
492     femarg_t		*arg,
493     vattr_t		*vap,
494     int			flags,
495     cred_t		*cr,
496     caller_context_t	*ct)
497 {
498 	int	rc = 0;
499 
500 	if (ct != &smb_ct && (vap->va_mask & AT_SIZE) != 0)
501 		rc = smb_fem_oplock_break(arg, ct, SMB_OPLOCK_BREAK_TO_NONE);
502 	if (rc == 0)
503 		rc = vnext_setattr(arg, vap, flags, cr, ct);
504 	return (rc);
505 }
506 
507 static int
508 smb_fem_oplock_rwlock(
509     femarg_t		*arg,
510     int			write_lock,
511     caller_context_t	*ct)
512 {
513 	uint32_t	flags;
514 	int		rc = 0;
515 
516 	if (ct != &smb_ct) {
517 		if (write_lock)
518 			flags = SMB_OPLOCK_BREAK_TO_NONE;
519 		else
520 			flags = SMB_OPLOCK_BREAK_TO_LEVEL_II;
521 		rc = smb_fem_oplock_break(arg, ct, flags);
522 	}
523 	if (rc == 0)
524 		rc = vnext_rwlock(arg, write_lock, ct);
525 
526 	return (rc);
527 }
528 
529 static int
530 smb_fem_oplock_space(
531     femarg_t		*arg,
532     int			cmd,
533     flock64_t		*bfp,
534     int			flag,
535     offset_t		offset,
536     cred_t		*cr,
537     caller_context_t	*ct)
538 {
539 	int	rc = 0;
540 
541 	if (ct != &smb_ct)
542 		rc = smb_fem_oplock_break(arg, ct, SMB_OPLOCK_BREAK_TO_NONE);
543 	if (rc == 0)
544 		rc = vnext_space(arg, cmd, bfp, flag, offset, cr, ct);
545 	return (rc);
546 }
547 
548 /*
549  * smb_fem_oplock_vnevent()
550  *
551  * To intercept NFS and local renames and removes in order to break any
552  * existing oplock prior to the operation.
553  *
554  * Note: Currently, this monitor is traversed only when an FS is mounted
555  * non-nbmand.  (When the FS is mounted nbmand, share reservation checking
556  * will detect a share violation and return an error prior to the VOP layer
557  * being reached.)  Thus, for nbmand NFS and local renames and removes,
558  * an existing oplock is never broken prior to share checking (contrary to
559  * how it is with intra-CIFS remove and rename requests).
560  */
561 
562 static int
563 smb_fem_oplock_vnevent(
564     femarg_t		*arg,
565     vnevent_t		vnevent,
566     vnode_t		*dvp,
567     char		*name,
568     caller_context_t	*ct)
569 {
570 	uint32_t	flags;
571 	int		rc = 0;
572 
573 	if (ct != &smb_ct) {
574 		switch (vnevent) {
575 		case VE_REMOVE:
576 		case VE_PRE_RENAME_DEST:
577 		case VE_RENAME_DEST:
578 			flags = SMB_OPLOCK_BREAK_TO_NONE |
579 			    SMB_OPLOCK_BREAK_BATCH;
580 			rc = smb_fem_oplock_break(arg, ct, flags);
581 			break;
582 		case VE_PRE_RENAME_SRC:
583 		case VE_RENAME_SRC:
584 			flags = SMB_OPLOCK_BREAK_TO_LEVEL_II |
585 			    SMB_OPLOCK_BREAK_BATCH;
586 			rc = smb_fem_oplock_break(arg, ct, flags);
587 			break;
588 		default:
589 			rc = 0;
590 			break;
591 		}
592 	}
593 	if (rc == 0)
594 		rc = vnext_vnevent(arg, vnevent, dvp, name, ct);
595 
596 	return (rc);
597 }
598 
599 static int
600 smb_fem_oplock_break(femarg_t *arg, caller_context_t *ct, uint32_t flags)
601 {
602 	smb_node_t	*node;
603 	int		rc;
604 
605 	node = (smb_node_t *)((arg)->fa_fnode->fn_available);
606 	SMB_NODE_VALID(node);
607 
608 	ASSERT(ct != &smb_ct);
609 
610 	if (ct && (ct->cc_flags & CC_DONTBLOCK)) {
611 		flags |= SMB_OPLOCK_BREAK_NOWAIT;
612 		rc = smb_oplock_break(NULL, node, flags);
613 		if (rc == EAGAIN)
614 			ct->cc_flags |= CC_WOULDBLOCK;
615 	} else {
616 		rc = smb_oplock_break(NULL, node, flags);
617 	}
618 
619 	return (rc);
620 }
621