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