xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb_fem.c (revision 45818ee124adeaaf947698996b4f4c722afc6d1f)
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  */
25 
26 #include <smbsrv/smb_kproto.h>
27 #include <smbsrv/smb_fsops.h>
28 #include <sys/sdt.h>
29 #include <sys/fcntl.h>
30 #include <sys/vfs.h>
31 #include <sys/vfs_opreg.h>
32 #include <sys/vnode.h>
33 #include <sys/fem.h>
34 
35 extern caller_context_t	smb_ct;
36 
37 static boolean_t	smb_fem_initialized = B_FALSE;
38 static fem_t		*smb_fcn_ops = NULL;
39 static fem_t		*smb_oplock_ops = NULL;
40 
41 /*
42  * Declarations for FCN (file change notification) FEM monitors
43  */
44 
45 static int smb_fem_fcn_create(femarg_t *, char *, vattr_t *, vcexcl_t, int,
46     vnode_t **, cred_t *, int, caller_context_t *, vsecattr_t *);
47 static int smb_fem_fcn_remove(femarg_t *, char *, cred_t *,
48     caller_context_t *, int);
49 static int smb_fem_fcn_rename(femarg_t *, char *, vnode_t *, char *,
50     cred_t *, caller_context_t *, int);
51 static int smb_fem_fcn_mkdir(femarg_t *, char *, vattr_t *, vnode_t **,
52     cred_t *, caller_context_t *, int, vsecattr_t *);
53 static int smb_fem_fcn_rmdir(femarg_t *, char *, vnode_t *, cred_t *,
54     caller_context_t *, int);
55 static int smb_fem_fcn_link(femarg_t *, vnode_t *, char *, cred_t *,
56     caller_context_t *, int);
57 static int smb_fem_fcn_symlink(femarg_t *, char *, vattr_t *,
58     char *, cred_t *, caller_context_t *, int);
59 
60 static const fs_operation_def_t smb_fcn_tmpl[] = {
61 	VOPNAME_CREATE, { .femop_create = smb_fem_fcn_create },
62 	VOPNAME_REMOVE, {.femop_remove = smb_fem_fcn_remove},
63 	VOPNAME_RENAME, {.femop_rename = smb_fem_fcn_rename},
64 	VOPNAME_MKDIR, {.femop_mkdir = smb_fem_fcn_mkdir},
65 	VOPNAME_RMDIR, {.femop_rmdir = smb_fem_fcn_rmdir},
66 	VOPNAME_LINK, {.femop_link = smb_fem_fcn_link},
67 	VOPNAME_SYMLINK, {.femop_symlink = smb_fem_fcn_symlink},
68 	NULL, NULL
69 };
70 
71 /*
72  * Declarations for oplock FEM monitors
73  */
74 
75 static int smb_fem_oplock_open(femarg_t *, int, cred_t *,
76     struct caller_context *);
77 static int smb_fem_oplock_read(femarg_t *, uio_t *, int, cred_t *,
78     struct caller_context *);
79 static int smb_fem_oplock_write(femarg_t *, uio_t *, int, cred_t *,
80     struct caller_context *);
81 static int smb_fem_oplock_setattr(femarg_t *, vattr_t *, int, cred_t *,
82     caller_context_t *);
83 static int smb_fem_oplock_rwlock(femarg_t *, int, 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_RWLOCK, { .femop_rwlock = smb_fem_oplock_rwlock },
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_break(femarg_t *, caller_context_t *, uint32_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 int
158 smb_fem_fcn_install(smb_node_t *node)
159 {
160 	int rc;
161 
162 	if (smb_fcn_ops == NULL)
163 		return (ENOSYS);
164 	rc = fem_install(node->vp, smb_fcn_ops, (void *)node, OPARGUNIQ,
165 	    (fem_func_t)smb_node_ref, (fem_func_t)smb_node_release);
166 	return (rc);
167 }
168 
169 void
170 smb_fem_fcn_uninstall(smb_node_t *node)
171 {
172 	if (smb_fcn_ops == NULL)
173 		return;
174 	VERIFY0(fem_uninstall(node->vp, smb_fcn_ops, (void *)node));
175 }
176 
177 int
178 smb_fem_oplock_install(smb_node_t *node)
179 {
180 	int rc;
181 
182 	if (smb_oplock_ops == NULL)
183 		return (ENOSYS);
184 	rc = fem_install(node->vp, smb_oplock_ops, (void *)node, OPARGUNIQ,
185 	    (fem_func_t)smb_node_ref, (fem_func_t)smb_node_release);
186 	return (rc);
187 }
188 
189 void
190 smb_fem_oplock_uninstall(smb_node_t *node)
191 {
192 	if (smb_oplock_ops == NULL)
193 		return;
194 	VERIFY0(fem_uninstall(node->vp, smb_oplock_ops, (void *)node));
195 }
196 
197 /*
198  * FEM FCN monitors
199  *
200  * The FCN monitors intercept the respective VOP_* call regardless
201  * of whether the call originates from CIFS, NFS, or a local process.
202  */
203 
204 /*
205  * smb_fem_fcn_create()
206  *
207  * This monitor will catch only changes to VREG files and not to extended
208  * attribute files.  This is fine because, for CIFS files, stream creates
209  * should not trigger any file change notification on the VDIR directory
210  * being monitored.  Creates of any other kind of extended attribute in
211  * the directory will also not trigger any file change notification on the
212  * VDIR directory being monitored.
213  */
214 
215 static int
216 smb_fem_fcn_create(
217     femarg_t *arg,
218     char *name,
219     vattr_t *vap,
220     vcexcl_t excl,
221     int mode,
222     vnode_t **vpp,
223     cred_t *cr,
224     int flag,
225     caller_context_t *ct,
226     vsecattr_t *vsecp)
227 {
228 	smb_node_t *dnode;
229 	int error;
230 
231 	dnode = (smb_node_t *)arg->fa_fnode->fn_available;
232 
233 	ASSERT(dnode);
234 
235 	error = vnext_create(arg, name, vap, excl, mode, vpp, cr, flag,
236 	    ct, vsecp);
237 
238 	if (error == 0 && ct != &smb_ct)
239 		smb_node_notify_change(dnode, FILE_ACTION_ADDED, name);
240 
241 	return (error);
242 }
243 
244 /*
245  * smb_fem_fcn_remove()
246  *
247  * This monitor will catch only changes to VREG files and to not extended
248  * attribute files.  This is fine because, for CIFS files, stream deletes
249  * should not trigger any file change notification on the VDIR directory
250  * being monitored.  Deletes of any other kind of extended attribute in
251  * the directory will also not trigger any file change notification on the
252  * VDIR directory being monitored.
253  */
254 
255 static int
256 smb_fem_fcn_remove(
257     femarg_t *arg,
258     char *name,
259     cred_t *cr,
260     caller_context_t *ct,
261     int flags)
262 {
263 	smb_node_t *dnode;
264 	int error;
265 
266 	dnode = (smb_node_t *)arg->fa_fnode->fn_available;
267 
268 	ASSERT(dnode);
269 
270 	error = vnext_remove(arg, name, cr, ct, flags);
271 
272 	if (error == 0 && ct != &smb_ct)
273 		smb_node_notify_change(dnode, FILE_ACTION_REMOVED, name);
274 
275 	return (error);
276 }
277 
278 static int
279 smb_fem_fcn_rename(
280     femarg_t *arg,
281     char *snm,
282     vnode_t *tdvp,
283     char *tnm,
284     cred_t *cr,
285     caller_context_t *ct,
286     int flags)
287 {
288 	smb_node_t *dnode;
289 	int error;
290 
291 	dnode = (smb_node_t *)arg->fa_fnode->fn_available;
292 
293 	ASSERT(dnode);
294 
295 	error = vnext_rename(arg, snm, tdvp, tnm, cr, ct, flags);
296 
297 	if (error == 0 && ct != &smb_ct) {
298 		/*
299 		 * Note that renames in the same directory are normally
300 		 * delivered in {old,new} pairs, and clients expect them
301 		 * in that order, if both events are delivered.
302 		 */
303 		smb_node_notify_change(dnode,
304 		    FILE_ACTION_RENAMED_OLD_NAME, snm);
305 		smb_node_notify_change(dnode,
306 		    FILE_ACTION_RENAMED_NEW_NAME, tnm);
307 	}
308 
309 	return (error);
310 }
311 
312 static int
313 smb_fem_fcn_mkdir(
314     femarg_t *arg,
315     char *name,
316     vattr_t *vap,
317     vnode_t **vpp,
318     cred_t *cr,
319     caller_context_t *ct,
320     int flags,
321     vsecattr_t *vsecp)
322 {
323 	smb_node_t *dnode;
324 	int error;
325 
326 	dnode = (smb_node_t *)arg->fa_fnode->fn_available;
327 
328 	ASSERT(dnode);
329 
330 	error = vnext_mkdir(arg, name, vap, vpp, cr, ct, flags, vsecp);
331 
332 	if (error == 0 && ct != &smb_ct)
333 		smb_node_notify_change(dnode, FILE_ACTION_ADDED, name);
334 
335 	return (error);
336 }
337 
338 static int
339 smb_fem_fcn_rmdir(
340     femarg_t *arg,
341     char *name,
342     vnode_t *cdir,
343     cred_t *cr,
344     caller_context_t *ct,
345     int flags)
346 {
347 	smb_node_t *dnode;
348 	int error;
349 
350 	dnode = (smb_node_t *)arg->fa_fnode->fn_available;
351 
352 	ASSERT(dnode);
353 
354 	error = vnext_rmdir(arg, name, cdir, cr, ct, flags);
355 
356 	if (error == 0 && ct != &smb_ct)
357 		smb_node_notify_change(dnode, FILE_ACTION_REMOVED, name);
358 
359 	return (error);
360 }
361 
362 static int
363 smb_fem_fcn_link(
364     femarg_t *arg,
365     vnode_t *svp,
366     char *tnm,
367     cred_t *cr,
368     caller_context_t *ct,
369     int flags)
370 {
371 	smb_node_t *dnode;
372 	int error;
373 
374 	dnode = (smb_node_t *)arg->fa_fnode->fn_available;
375 
376 	ASSERT(dnode);
377 
378 	error = vnext_link(arg, svp, tnm, cr, ct, flags);
379 
380 	if (error == 0 && ct != &smb_ct)
381 		smb_node_notify_change(dnode, FILE_ACTION_ADDED, tnm);
382 
383 	return (error);
384 }
385 
386 static int
387 smb_fem_fcn_symlink(
388     femarg_t *arg,
389     char *linkname,
390     vattr_t *vap,
391     char *target,
392     cred_t *cr,
393     caller_context_t *ct,
394     int flags)
395 {
396 	smb_node_t *dnode;
397 	int error;
398 
399 	dnode = (smb_node_t *)arg->fa_fnode->fn_available;
400 
401 	ASSERT(dnode);
402 
403 	error = vnext_symlink(arg, linkname, vap, target, cr, ct, flags);
404 
405 	if (error == 0 && ct != &smb_ct)
406 		smb_node_notify_change(dnode, FILE_ACTION_ADDED, linkname);
407 
408 	return (error);
409 }
410 
411 /*
412  * FEM oplock monitors
413  *
414  * The monitors below are not intended to intercept CIFS calls.
415  * CIFS higher-level routines will break oplocks as needed prior
416  * to getting to the VFS layer.
417  */
418 static int
419 smb_fem_oplock_open(
420     femarg_t		*arg,
421     int			mode,
422     cred_t		*cr,
423     caller_context_t	*ct)
424 {
425 	uint32_t	flags;
426 	int		rc = 0;
427 
428 	if (ct != &smb_ct) {
429 		if (mode & (FWRITE|FTRUNC))
430 			flags = SMB_OPLOCK_BREAK_TO_NONE;
431 		else
432 			flags = SMB_OPLOCK_BREAK_TO_LEVEL_II;
433 		rc = smb_fem_oplock_break(arg, ct, flags);
434 	}
435 	if (rc == 0)
436 		rc = vnext_open(arg, mode, cr, ct);
437 
438 	return (rc);
439 }
440 
441 /*
442  * Should normally be hit only via NFSv2/v3.  All other accesses
443  * (CIFS/NFS/local) should call VOP_OPEN first.
444  */
445 
446 static int
447 smb_fem_oplock_read(
448     femarg_t		*arg,
449     uio_t		*uiop,
450     int			ioflag,
451     cred_t		*cr,
452     caller_context_t	*ct)
453 {
454 	int	rc = 0;
455 
456 	if (ct != &smb_ct) {
457 		rc = smb_fem_oplock_break(arg, ct,
458 		    SMB_OPLOCK_BREAK_TO_LEVEL_II);
459 	}
460 	if (rc == 0)
461 		rc = vnext_read(arg, uiop, ioflag, 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_write(
473     femarg_t		*arg,
474     uio_t		*uiop,
475     int			ioflag,
476     cred_t		*cr,
477     caller_context_t	*ct)
478 {
479 	int	rc = 0;
480 
481 	if (ct != &smb_ct)
482 		rc = smb_fem_oplock_break(arg, ct, SMB_OPLOCK_BREAK_TO_NONE);
483 	if (rc == 0)
484 		rc = vnext_write(arg, uiop, ioflag, cr, ct);
485 
486 	return (rc);
487 }
488 
489 static int
490 smb_fem_oplock_setattr(
491     femarg_t		*arg,
492     vattr_t		*vap,
493     int			flags,
494     cred_t		*cr,
495     caller_context_t	*ct)
496 {
497 	int	rc = 0;
498 
499 	if (ct != &smb_ct && (vap->va_mask & AT_SIZE) != 0)
500 		rc = smb_fem_oplock_break(arg, ct, SMB_OPLOCK_BREAK_TO_NONE);
501 	if (rc == 0)
502 		rc = vnext_setattr(arg, vap, flags, cr, ct);
503 	return (rc);
504 }
505 
506 static int
507 smb_fem_oplock_rwlock(
508     femarg_t		*arg,
509     int			write_lock,
510     caller_context_t	*ct)
511 {
512 	uint32_t	flags;
513 	int		rc = 0;
514 
515 	if (ct != &smb_ct) {
516 		if (write_lock)
517 			flags = SMB_OPLOCK_BREAK_TO_NONE;
518 		else
519 			flags = SMB_OPLOCK_BREAK_TO_LEVEL_II;
520 		rc = smb_fem_oplock_break(arg, ct, flags);
521 	}
522 	if (rc == 0)
523 		rc = vnext_rwlock(arg, write_lock, ct);
524 
525 	return (rc);
526 }
527 
528 static int
529 smb_fem_oplock_space(
530     femarg_t		*arg,
531     int			cmd,
532     flock64_t		*bfp,
533     int			flag,
534     offset_t		offset,
535     cred_t		*cr,
536     caller_context_t	*ct)
537 {
538 	int	rc = 0;
539 
540 	if (ct != &smb_ct)
541 		rc = smb_fem_oplock_break(arg, ct, SMB_OPLOCK_BREAK_TO_NONE);
542 	if (rc == 0)
543 		rc = vnext_space(arg, cmd, bfp, flag, offset, cr, ct);
544 	return (rc);
545 }
546 
547 /*
548  * smb_fem_oplock_vnevent()
549  *
550  * To intercept NFS and local renames and removes in order to break any
551  * existing oplock prior to the operation.
552  *
553  * Note: Currently, this monitor is traversed only when an FS is mounted
554  * non-nbmand.  (When the FS is mounted nbmand, share reservation checking
555  * will detect a share violation and return an error prior to the VOP layer
556  * being reached.)  Thus, for nbmand NFS and local renames and removes,
557  * an existing oplock is never broken prior to share checking (contrary to
558  * how it is with intra-CIFS remove and rename requests).
559  */
560 
561 static int
562 smb_fem_oplock_vnevent(
563     femarg_t		*arg,
564     vnevent_t		vnevent,
565     vnode_t		*dvp,
566     char		*name,
567     caller_context_t	*ct)
568 {
569 	uint32_t	flags;
570 	int		rc = 0;
571 
572 	if (ct != &smb_ct) {
573 		switch (vnevent) {
574 		case VE_REMOVE:
575 		case VE_RENAME_DEST:
576 			flags = SMB_OPLOCK_BREAK_TO_NONE |
577 			    SMB_OPLOCK_BREAK_BATCH;
578 			rc = smb_fem_oplock_break(arg, ct, flags);
579 			break;
580 		case VE_RENAME_SRC:
581 			flags = SMB_OPLOCK_BREAK_TO_LEVEL_II |
582 			    SMB_OPLOCK_BREAK_BATCH;
583 			rc = smb_fem_oplock_break(arg, ct, flags);
584 			break;
585 		default:
586 			rc = 0;
587 			break;
588 		}
589 	}
590 	if (rc == 0)
591 		rc = vnext_vnevent(arg, vnevent, dvp, name, ct);
592 
593 	return (rc);
594 }
595 
596 static int
597 smb_fem_oplock_break(femarg_t *arg, caller_context_t *ct, uint32_t flags)
598 {
599 	smb_node_t	*node;
600 	int		rc;
601 
602 	node = (smb_node_t *)((arg)->fa_fnode->fn_available);
603 	SMB_NODE_VALID(node);
604 
605 	ASSERT(ct != &smb_ct);
606 
607 	if (ct && (ct->cc_flags & CC_DONTBLOCK)) {
608 		flags |= SMB_OPLOCK_BREAK_NOWAIT;
609 		rc = smb_oplock_break(NULL, node, flags);
610 		if (rc == EAGAIN)
611 			ct->cc_flags |= CC_WOULDBLOCK;
612 	} else {
613 		rc = smb_oplock_break(NULL, node, flags);
614 	}
615 
616 	return (rc);
617 }
618