xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb_fem.c (revision 8b80e8cb6855118d46f605e91b5ed4ce83417395)
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 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 #include <smbsrv/smb_incl.h>
29 #include <smbsrv/smb_fsops.h>
30 #include <sys/sdt.h>
31 #include <sys/fcntl.h>
32 #include <sys/vfs.h>
33 #include <sys/vfs_opreg.h>
34 #include <sys/vnode.h>
35 #include <sys/fem.h>
36 
37 extern caller_context_t	smb_ct;
38 
39 static boolean_t	smb_fem_initialized = B_FALSE;
40 static fem_t		*smb_fcn_ops = NULL;
41 static fem_t		*smb_oplock_ops = NULL;
42 
43 /*
44  * Declarations for FCN (file change notification) FEM monitors
45  */
46 
47 void smb_fem_fcn_install(smb_node_t *);
48 void smb_fem_fcn_uninstall(smb_node_t *);
49 
50 static int smb_fem_fcn_create(femarg_t *, char *, vattr_t *, vcexcl_t, int,
51     vnode_t **, cred_t *, int, caller_context_t *, vsecattr_t *);
52 static int smb_fem_fcn_remove(femarg_t *, char *, cred_t *,
53     caller_context_t *, int);
54 static int smb_fem_fcn_rename(femarg_t *, char *, vnode_t *, char *,
55     cred_t *, caller_context_t *, int);
56 static int smb_fem_fcn_mkdir(femarg_t *, char *, vattr_t *, vnode_t **,
57     cred_t *, caller_context_t *, int, vsecattr_t *);
58 static int smb_fem_fcn_rmdir(femarg_t *, char *, vnode_t *, cred_t *,
59     caller_context_t *, int);
60 static int smb_fem_fcn_link(femarg_t *, vnode_t *, char *, cred_t *,
61     caller_context_t *, int);
62 static int smb_fem_fcn_symlink(femarg_t *, char *, vattr_t *,
63     char *, cred_t *, caller_context_t *, int);
64 
65 static const fs_operation_def_t smb_fcn_tmpl[] = {
66 	VOPNAME_CREATE, { .femop_create = smb_fem_fcn_create },
67 	VOPNAME_REMOVE, {.femop_remove = smb_fem_fcn_remove},
68 	VOPNAME_RENAME, {.femop_rename = smb_fem_fcn_rename},
69 	VOPNAME_MKDIR, {.femop_mkdir = smb_fem_fcn_mkdir},
70 	VOPNAME_RMDIR, {.femop_rmdir = smb_fem_fcn_rmdir},
71 	VOPNAME_LINK, {.femop_link = smb_fem_fcn_link},
72 	VOPNAME_SYMLINK, {.femop_symlink = smb_fem_fcn_symlink},
73 	NULL, NULL
74 };
75 
76 /*
77  * Declarations for oplock FEM monitors
78  */
79 
80 int smb_fem_oplock_install(smb_node_t *);
81 void smb_fem_oplock_uninstall(smb_node_t *);
82 
83 static int smb_fem_oplock_open(femarg_t *, int, cred_t *,
84     struct caller_context *);
85 static int smb_fem_oplock_read(femarg_t *, uio_t *, int, cred_t *,
86     struct caller_context *);
87 static int smb_fem_oplock_write(femarg_t *, uio_t *, int, cred_t *,
88     struct caller_context *);
89 static int smb_fem_oplock_setattr(femarg_t *, vattr_t *, int, cred_t *,
90     caller_context_t *);
91 static int smb_fem_oplock_rwlock(femarg_t *, int, caller_context_t *);
92 static int smb_fem_oplock_space(femarg_t *, int, flock64_t *, int,
93     offset_t, cred_t *, caller_context_t *);
94 static int smb_fem_oplock_setsecattr(femarg_t *, vsecattr_t *, int, cred_t *,
95     caller_context_t *);
96 static int smb_fem_oplock_vnevent(femarg_t *, vnevent_t, vnode_t *, char *,
97     caller_context_t *);
98 
99 static const fs_operation_def_t smb_oplock_tmpl[] = {
100 	VOPNAME_OPEN,	{ .femop_open = smb_fem_oplock_open },
101 	VOPNAME_READ,	{ .femop_read = smb_fem_oplock_read },
102 	VOPNAME_WRITE,	{ .femop_write = smb_fem_oplock_write },
103 	VOPNAME_SETATTR, { .femop_setattr = smb_fem_oplock_setattr },
104 	VOPNAME_RWLOCK, { .femop_rwlock = smb_fem_oplock_rwlock },
105 	VOPNAME_SPACE,	{ .femop_space = smb_fem_oplock_space },
106 	VOPNAME_SETSECATTR, { .femop_setsecattr = smb_fem_oplock_setsecattr },
107 	VOPNAME_VNEVENT, { .femop_vnevent = smb_fem_oplock_vnevent },
108 	NULL, NULL
109 };
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 
401 #define	SMB_FEM_OPLOCK_BREAK(arg) \
402 	smb_oplock_break((smb_node_t *)((arg)->fa_fnode->fn_available));
403 
404 static int
405 smb_fem_oplock_open(
406 	femarg_t *arg,
407 	int mode,
408 	cred_t *cr,
409 	struct caller_context *ct)
410 {
411 	if (ct == NULL || ct->cc_caller_id != smb_ct.cc_caller_id)
412 		SMB_FEM_OPLOCK_BREAK(arg);
413 
414 	return (vnext_open(arg, mode, cr, ct));
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 	struct caller_context *ct)
429 {
430 	if (ct == NULL || ct->cc_caller_id != smb_ct.cc_caller_id)
431 		SMB_FEM_OPLOCK_BREAK(arg);
432 
433 	return (vnext_read(arg, uiop, ioflag, cr, ct));
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 	struct caller_context *ct)
448 {
449 	if (ct == NULL || ct->cc_caller_id != smb_ct.cc_caller_id)
450 		SMB_FEM_OPLOCK_BREAK(arg);
451 
452 	return (vnext_write(arg, uiop, ioflag, cr, ct));
453 }
454 
455 static int
456 smb_fem_oplock_setattr(
457 	femarg_t *arg,
458 	vattr_t *vap,
459 	int flags,
460 	cred_t *cr,
461 	caller_context_t *ct)
462 {
463 	if (ct == NULL || ct->cc_caller_id != smb_ct.cc_caller_id)
464 		SMB_FEM_OPLOCK_BREAK(arg);
465 
466 	return (vnext_setattr(arg, vap, flags, cr, ct));
467 }
468 
469 static int
470 smb_fem_oplock_rwlock(
471 	femarg_t *arg,
472 	int write_lock,
473 	caller_context_t *ct)
474 {
475 	if (write_lock) {
476 		if (ct == NULL || ct->cc_caller_id != smb_ct.cc_caller_id)
477 			SMB_FEM_OPLOCK_BREAK(arg);
478 	}
479 
480 	return (vnext_rwlock(arg, write_lock, ct));
481 }
482 
483 static int
484 smb_fem_oplock_space(
485 	femarg_t *arg,
486 	int cmd,
487 	flock64_t *bfp,
488 	int flag,
489 	offset_t offset,
490 	cred_t *cr,
491 	caller_context_t *ct)
492 {
493 	if (ct == NULL || ct->cc_caller_id != smb_ct.cc_caller_id)
494 		SMB_FEM_OPLOCK_BREAK(arg);
495 
496 	return (vnext_space(arg, cmd, bfp, flag, offset, cr, ct));
497 }
498 
499 static int
500 smb_fem_oplock_setsecattr(
501 	femarg_t *arg,
502 	vsecattr_t *vsap,
503 	int flag,
504 	cred_t *cr,
505 	caller_context_t *ct)
506 {
507 	if (ct == NULL || ct->cc_caller_id != smb_ct.cc_caller_id)
508 		SMB_FEM_OPLOCK_BREAK(arg);
509 
510 	return (vnext_setsecattr(arg, vsap, flag, cr, ct));
511 }
512 
513 /*
514  * smb_fem_oplock_vnevent()
515  *
516  * To intercept NFS and local renames and removes in order to break any
517  * existing oplock prior to the operation.
518  *
519  * Note: Currently, this monitor is traversed only when an FS is mounted
520  * non-nbmand.  (When the FS is mounted nbmand, share reservation checking
521  * will detect a share violation and return an error prior to the VOP layer
522  * being reached.)  Thus, for nbmand NFS and local renames and removes,
523  * an existing oplock is never broken prior to share checking (contrary to
524  * how it is with intra-CIFS remove and rename requests).
525  */
526 
527 static int
528 smb_fem_oplock_vnevent(
529 	femarg_t *arg,
530 	vnevent_t vnevent,
531 	vnode_t *dvp,
532 	char *name,
533 	caller_context_t *ct)
534 {
535 	switch (vnevent) {
536 	case VE_REMOVE:
537 	case VE_RENAME_DEST:
538 	case VE_RENAME_SRC:
539 
540 		SMB_FEM_OPLOCK_BREAK(arg);
541 		break;
542 
543 	default:
544 		break;
545 	}
546 	return (vnext_vnevent(arg, vnevent, dvp, name, ct));
547 }
548