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
smb_fem_init(void)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
smb_fem_fini(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
smb_fem_fcn_install(smb_node_t * node)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
smb_fem_fcn_uninstall(smb_node_t * node)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
smb_fem_oplock_install(smb_node_t * node)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
smb_fem_oplock_uninstall(smb_node_t * node)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
smb_fem_fcn_create(femarg_t * arg,char * name,vattr_t * vap,vcexcl_t excl,int mode,vnode_t ** vpp,cred_t * cr,int flag,caller_context_t * ct,vsecattr_t * vsecp)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
smb_fem_fcn_remove(femarg_t * arg,char * name,cred_t * cr,caller_context_t * ct,int flags)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
smb_fem_fcn_rename(femarg_t * arg,char * snm,vnode_t * tdvp,char * tnm,cred_t * cr,caller_context_t * ct,int flags)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
smb_fem_fcn_mkdir(femarg_t * arg,char * name,vattr_t * vap,vnode_t ** vpp,cred_t * cr,caller_context_t * ct,int flags,vsecattr_t * vsecp)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
smb_fem_fcn_rmdir(femarg_t * arg,char * name,vnode_t * cdir,cred_t * cr,caller_context_t * ct,int flags)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
smb_fem_fcn_link(femarg_t * arg,vnode_t * svp,char * tnm,cred_t * cr,caller_context_t * ct,int flags)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
smb_fem_fcn_symlink(femarg_t * arg,char * linkname,vattr_t * vap,char * target,cred_t * cr,caller_context_t * ct,int flags)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
smb_fem_oplock_open(femarg_t * arg,int mode,cred_t * cr,caller_context_t * ct)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
smb_fem_oplock_read(femarg_t * arg,uio_t * uiop,int ioflag,cred_t * cr,caller_context_t * ct)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
smb_fem_oplock_write(femarg_t * arg,uio_t * uiop,int ioflag,cred_t * cr,caller_context_t * ct)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
smb_fem_oplock_setattr(femarg_t * arg,vattr_t * vap,int flags,cred_t * cr,caller_context_t * ct)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
smb_fem_oplock_rwlock(femarg_t * arg,int write_lock,caller_context_t * ct)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
smb_fem_oplock_space(femarg_t * arg,int cmd,flock64_t * bfp,int flag,offset_t offset,cred_t * cr,caller_context_t * ct)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
smb_fem_oplock_vnevent(femarg_t * arg,vnevent_t vnevent,vnode_t * dvp,char * name,caller_context_t * ct)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
smb_fem_oplock_break(femarg_t * arg,caller_context_t * ct,uint32_t flags)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