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