smb_fem.c revision 11963:061945695ce1
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 2010 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
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
35extern caller_context_t	smb_ct;
36
37static boolean_t	smb_fem_initialized = B_FALSE;
38static fem_t		*smb_fcn_ops = NULL;
39static fem_t		*smb_oplock_ops = NULL;
40
41/*
42 * Declarations for FCN (file change notification) FEM monitors
43 */
44
45void smb_fem_fcn_install(smb_node_t *);
46void smb_fem_fcn_uninstall(smb_node_t *);
47
48static int smb_fem_fcn_create(femarg_t *, char *, vattr_t *, vcexcl_t, int,
49    vnode_t **, cred_t *, int, caller_context_t *, vsecattr_t *);
50static int smb_fem_fcn_remove(femarg_t *, char *, cred_t *,
51    caller_context_t *, int);
52static int smb_fem_fcn_rename(femarg_t *, char *, vnode_t *, char *,
53    cred_t *, caller_context_t *, int);
54static int smb_fem_fcn_mkdir(femarg_t *, char *, vattr_t *, vnode_t **,
55    cred_t *, caller_context_t *, int, vsecattr_t *);
56static int smb_fem_fcn_rmdir(femarg_t *, char *, vnode_t *, cred_t *,
57    caller_context_t *, int);
58static int smb_fem_fcn_link(femarg_t *, vnode_t *, char *, cred_t *,
59    caller_context_t *, int);
60static int smb_fem_fcn_symlink(femarg_t *, char *, vattr_t *,
61    char *, cred_t *, caller_context_t *, int);
62
63static 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
78int smb_fem_oplock_install(smb_node_t *);
79void smb_fem_oplock_uninstall(smb_node_t *);
80
81static int smb_fem_oplock_open(femarg_t *, int, cred_t *,
82    struct caller_context *);
83static int smb_fem_oplock_read(femarg_t *, uio_t *, int, cred_t *,
84    struct caller_context *);
85static int smb_fem_oplock_write(femarg_t *, uio_t *, int, cred_t *,
86    struct caller_context *);
87static int smb_fem_oplock_setattr(femarg_t *, vattr_t *, int, cred_t *,
88    caller_context_t *);
89static int smb_fem_oplock_rwlock(femarg_t *, int, caller_context_t *);
90static int smb_fem_oplock_space(femarg_t *, int, flock64_t *, int,
91    offset_t, cred_t *, caller_context_t *);
92static int smb_fem_oplock_setsecattr(femarg_t *, vsecattr_t *, int, cred_t *,
93    caller_context_t *);
94static int smb_fem_oplock_vnevent(femarg_t *, vnevent_t, vnode_t *, char *,
95    caller_context_t *);
96
97static 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
109static 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 */
117int
118smb_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 */
149void
150smb_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
162void
163smb_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
169void
170smb_fem_fcn_uninstall(smb_node_t *node)
171{
172	(void) fem_uninstall(node->vp, smb_fcn_ops, (void *)node);
173}
174
175int
176smb_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
182void
183smb_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
206static int
207smb_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_node_notify_change(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
246static int
247smb_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_node_notify_change(dnode);
265
266	return (error);
267}
268
269static int
270smb_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_node_notify_change(dnode);
290
291	return (error);
292}
293
294static int
295smb_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_node_notify_change(dnode);
316
317	return (error);
318}
319
320static int
321smb_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_node_notify_change(dnode);
340
341	return (error);
342}
343
344static int
345smb_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_node_notify_change(dnode);
364
365	return (error);
366}
367
368static int
369smb_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_node_notify_change(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 */
400static int
401smb_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
420static int
421smb_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
441static int
442smb_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
457static int
458smb_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
473static int
474smb_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
489static int
490smb_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
507static int
508smb_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
537static int
538smb_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
562static int
563smb_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