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