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