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 2020 Tintri by DDN, Inc. All rights reserved.
24 * Copyright 2015 Joyent, Inc.
25 */
26
27 #include <smbsrv/smb_kproto.h>
28 #include <smbsrv/smb_fsops.h>
29 #include <sys/sdt.h>
30 #include <sys/fcntl.h>
31 #include <sys/vfs.h>
32 #include <sys/vfs_opreg.h>
33 #include <sys/vnode.h>
34 #include <sys/fem.h>
35
36 extern caller_context_t smb_ct;
37
38 static boolean_t smb_fem_initialized = B_FALSE;
39 static fem_t *smb_fcn_ops = NULL;
40 static fem_t *smb_oplock_ops = NULL;
41
42 /*
43 * Declarations for FCN (file change notification) FEM monitors
44 */
45
46 static int smb_fem_fcn_create(femarg_t *, char *, vattr_t *, vcexcl_t, int,
47 vnode_t **, cred_t *, int, caller_context_t *, vsecattr_t *);
48 static int smb_fem_fcn_remove(femarg_t *, char *, cred_t *,
49 caller_context_t *, int);
50 static int smb_fem_fcn_rename(femarg_t *, char *, vnode_t *, char *,
51 cred_t *, caller_context_t *, int);
52 static int smb_fem_fcn_mkdir(femarg_t *, char *, vattr_t *, vnode_t **,
53 cred_t *, caller_context_t *, int, vsecattr_t *);
54 static int smb_fem_fcn_rmdir(femarg_t *, char *, vnode_t *, cred_t *,
55 caller_context_t *, int);
56 static int smb_fem_fcn_link(femarg_t *, vnode_t *, char *, cred_t *,
57 caller_context_t *, int);
58 static int smb_fem_fcn_symlink(femarg_t *, char *, vattr_t *,
59 char *, cred_t *, caller_context_t *, int);
60
61 static const fs_operation_def_t smb_fcn_tmpl[] = {
62 VOPNAME_CREATE, { .femop_create = smb_fem_fcn_create },
63 VOPNAME_REMOVE, {.femop_remove = smb_fem_fcn_remove},
64 VOPNAME_RENAME, {.femop_rename = smb_fem_fcn_rename},
65 VOPNAME_MKDIR, {.femop_mkdir = smb_fem_fcn_mkdir},
66 VOPNAME_RMDIR, {.femop_rmdir = smb_fem_fcn_rmdir},
67 VOPNAME_LINK, {.femop_link = smb_fem_fcn_link},
68 VOPNAME_SYMLINK, {.femop_symlink = smb_fem_fcn_symlink},
69 NULL, NULL
70 };
71
72 /*
73 * Declarations for oplock FEM monitors
74 */
75
76 static int smb_fem_oplock_open(femarg_t *, int, cred_t *,
77 struct caller_context *);
78 static int smb_fem_oplock_read(femarg_t *, uio_t *, int, cred_t *,
79 struct caller_context *);
80 static int smb_fem_oplock_write(femarg_t *, uio_t *, int, cred_t *,
81 struct caller_context *);
82 static int smb_fem_oplock_setattr(femarg_t *, vattr_t *, int, cred_t *,
83 caller_context_t *);
84 static int smb_fem_oplock_space(femarg_t *, int, flock64_t *, int,
85 offset_t, cred_t *, caller_context_t *);
86 static int smb_fem_oplock_vnevent(femarg_t *, vnevent_t, vnode_t *, char *,
87 caller_context_t *);
88
89 static const fs_operation_def_t smb_oplock_tmpl[] = {
90 VOPNAME_OPEN, { .femop_open = smb_fem_oplock_open },
91 VOPNAME_READ, { .femop_read = smb_fem_oplock_read },
92 VOPNAME_WRITE, { .femop_write = smb_fem_oplock_write },
93 VOPNAME_SETATTR, { .femop_setattr = smb_fem_oplock_setattr },
94 VOPNAME_SPACE, { .femop_space = smb_fem_oplock_space },
95 VOPNAME_VNEVENT, { .femop_vnevent = smb_fem_oplock_vnevent },
96 NULL, NULL
97 };
98
99 static int smb_fem_oplock_wait(smb_node_t *, caller_context_t *);
100
101 /*
102 * smb_fem_init
103 *
104 * This function is not multi-thread safe. The caller must make sure only one
105 * thread makes the call.
106 */
107 int
smb_fem_init(void)108 smb_fem_init(void)
109 {
110 int rc = 0;
111
112 if (smb_fem_initialized)
113 return (0);
114
115 rc = fem_create("smb_fcn_ops", smb_fcn_tmpl, &smb_fcn_ops);
116 if (rc)
117 return (rc);
118
119 rc = fem_create("smb_oplock_ops", smb_oplock_tmpl,
120 &smb_oplock_ops);
121
122 if (rc) {
123 fem_free(smb_fcn_ops);
124 smb_fcn_ops = NULL;
125 return (rc);
126 }
127
128 smb_fem_initialized = B_TRUE;
129
130 return (0);
131 }
132
133 /*
134 * smb_fem_fini
135 *
136 * This function is not multi-thread safe. The caller must make sure only one
137 * thread makes the call.
138 */
139 void
smb_fem_fini(void)140 smb_fem_fini(void)
141 {
142 if (!smb_fem_initialized)
143 return;
144
145 if (smb_fcn_ops != NULL) {
146 fem_free(smb_fcn_ops);
147 smb_fcn_ops = NULL;
148 }
149 if (smb_oplock_ops != NULL) {
150 fem_free(smb_oplock_ops);
151 smb_oplock_ops = NULL;
152 }
153 smb_fem_initialized = B_FALSE;
154 }
155
156 /*
157 * Install our fem hooks for change notify.
158 * Not using hold/rele function here because we
159 * remove the fem hooks before node destroy.
160 */
161 int
smb_fem_fcn_install(smb_node_t * node)162 smb_fem_fcn_install(smb_node_t *node)
163 {
164 int rc;
165
166 if (smb_fcn_ops == NULL)
167 return (ENOSYS);
168 rc = fem_install(node->vp, smb_fcn_ops, (void *)node, OPARGUNIQ,
169 NULL, NULL);
170 return (rc);
171 }
172
173 int
smb_fem_fcn_uninstall(smb_node_t * node)174 smb_fem_fcn_uninstall(smb_node_t *node)
175 {
176 int rc;
177
178 if (smb_fcn_ops == NULL)
179 return (ENOSYS);
180 rc = fem_uninstall(node->vp, smb_fcn_ops, (void *)node);
181 return (rc);
182 }
183
184 int
smb_fem_oplock_install(smb_node_t * node)185 smb_fem_oplock_install(smb_node_t *node)
186 {
187 int rc;
188
189 if (smb_oplock_ops == NULL)
190 return (ENOSYS);
191 rc = fem_install(node->vp, smb_oplock_ops, (void *)node, OPARGUNIQ,
192 (fem_func_t)smb_node_ref, (fem_func_t)smb_node_release);
193 return (rc);
194 }
195
196 void
smb_fem_oplock_uninstall(smb_node_t * node)197 smb_fem_oplock_uninstall(smb_node_t *node)
198 {
199 if (smb_oplock_ops == NULL)
200 return;
201 VERIFY0(fem_uninstall(node->vp, smb_oplock_ops, (void *)node));
202 }
203
204 /*
205 * FEM FCN monitors
206 *
207 * The FCN monitors intercept the respective VOP_* call regardless
208 * of whether the call originates from CIFS, NFS, or a local process.
209 */
210
211 /*
212 * smb_fem_fcn_create()
213 *
214 * This monitor will catch only changes to VREG files and not to extended
215 * attribute files. This is fine because, for CIFS files, stream creates
216 * should not trigger any file change notification on the VDIR directory
217 * being monitored. Creates of any other kind of extended attribute in
218 * the directory will also not trigger any file change notification on the
219 * VDIR directory being monitored.
220 */
221
222 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)223 smb_fem_fcn_create(
224 femarg_t *arg,
225 char *name,
226 vattr_t *vap,
227 vcexcl_t excl,
228 int mode,
229 vnode_t **vpp,
230 cred_t *cr,
231 int flag,
232 caller_context_t *ct,
233 vsecattr_t *vsecp)
234 {
235 smb_node_t *dnode;
236 int error;
237
238 dnode = (smb_node_t *)arg->fa_fnode->fn_available;
239
240 ASSERT(dnode);
241
242 error = vnext_create(arg, name, vap, excl, mode, vpp, cr, flag,
243 ct, vsecp);
244
245 if (error == 0 && ct != &smb_ct)
246 smb_node_notify_change(dnode, FILE_ACTION_ADDED, name);
247
248 return (error);
249 }
250
251 /*
252 * smb_fem_fcn_remove()
253 *
254 * This monitor will catch only changes to VREG files and to not extended
255 * attribute files. This is fine because, for CIFS files, stream deletes
256 * should not trigger any file change notification on the VDIR directory
257 * being monitored. Deletes of any other kind of extended attribute in
258 * the directory will also not trigger any file change notification on the
259 * VDIR directory being monitored.
260 */
261
262 static int
smb_fem_fcn_remove(femarg_t * arg,char * name,cred_t * cr,caller_context_t * ct,int flags)263 smb_fem_fcn_remove(
264 femarg_t *arg,
265 char *name,
266 cred_t *cr,
267 caller_context_t *ct,
268 int flags)
269 {
270 smb_node_t *dnode;
271 int error;
272
273 dnode = (smb_node_t *)arg->fa_fnode->fn_available;
274
275 ASSERT(dnode);
276
277 error = vnext_remove(arg, name, cr, ct, flags);
278
279 if (error == 0 && ct != &smb_ct)
280 smb_node_notify_change(dnode, FILE_ACTION_REMOVED, name);
281
282 return (error);
283 }
284
285 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)286 smb_fem_fcn_rename(
287 femarg_t *arg,
288 char *snm,
289 vnode_t *tdvp,
290 char *tnm,
291 cred_t *cr,
292 caller_context_t *ct,
293 int flags)
294 {
295 smb_node_t *dnode;
296 int error;
297
298 dnode = (smb_node_t *)arg->fa_fnode->fn_available;
299
300 ASSERT(dnode);
301
302 error = vnext_rename(arg, snm, tdvp, tnm, cr, ct, flags);
303
304 if (error == 0 && ct != &smb_ct) {
305 /*
306 * Note that renames in the same directory are normally
307 * delivered in {old,new} pairs, and clients expect them
308 * in that order, if both events are delivered.
309 */
310 smb_node_notify_change(dnode,
311 FILE_ACTION_RENAMED_OLD_NAME, snm);
312 smb_node_notify_change(dnode,
313 FILE_ACTION_RENAMED_NEW_NAME, tnm);
314 }
315
316 return (error);
317 }
318
319 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)320 smb_fem_fcn_mkdir(
321 femarg_t *arg,
322 char *name,
323 vattr_t *vap,
324 vnode_t **vpp,
325 cred_t *cr,
326 caller_context_t *ct,
327 int flags,
328 vsecattr_t *vsecp)
329 {
330 smb_node_t *dnode;
331 int error;
332
333 dnode = (smb_node_t *)arg->fa_fnode->fn_available;
334
335 ASSERT(dnode);
336
337 error = vnext_mkdir(arg, name, vap, vpp, cr, ct, flags, vsecp);
338
339 if (error == 0 && ct != &smb_ct)
340 smb_node_notify_change(dnode, FILE_ACTION_ADDED, name);
341
342 return (error);
343 }
344
345 static int
smb_fem_fcn_rmdir(femarg_t * arg,char * name,vnode_t * cdir,cred_t * cr,caller_context_t * ct,int flags)346 smb_fem_fcn_rmdir(
347 femarg_t *arg,
348 char *name,
349 vnode_t *cdir,
350 cred_t *cr,
351 caller_context_t *ct,
352 int flags)
353 {
354 smb_node_t *dnode;
355 int error;
356
357 dnode = (smb_node_t *)arg->fa_fnode->fn_available;
358
359 ASSERT(dnode);
360
361 error = vnext_rmdir(arg, name, cdir, cr, ct, flags);
362
363 if (error == 0 && ct != &smb_ct)
364 smb_node_notify_change(dnode, FILE_ACTION_REMOVED, name);
365
366 return (error);
367 }
368
369 static int
smb_fem_fcn_link(femarg_t * arg,vnode_t * svp,char * tnm,cred_t * cr,caller_context_t * ct,int flags)370 smb_fem_fcn_link(
371 femarg_t *arg,
372 vnode_t *svp,
373 char *tnm,
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_link(arg, svp, tnm, cr, ct, flags);
386
387 if (error == 0 && ct != &smb_ct)
388 smb_node_notify_change(dnode, FILE_ACTION_ADDED, tnm);
389
390 return (error);
391 }
392
393 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)394 smb_fem_fcn_symlink(
395 femarg_t *arg,
396 char *linkname,
397 vattr_t *vap,
398 char *target,
399 cred_t *cr,
400 caller_context_t *ct,
401 int flags)
402 {
403 smb_node_t *dnode;
404 int error;
405
406 dnode = (smb_node_t *)arg->fa_fnode->fn_available;
407
408 ASSERT(dnode);
409
410 error = vnext_symlink(arg, linkname, vap, target, cr, ct, flags);
411
412 if (error == 0 && ct != &smb_ct)
413 smb_node_notify_change(dnode, FILE_ACTION_ADDED, linkname);
414
415 return (error);
416 }
417
418 /*
419 * FEM oplock monitors
420 *
421 * The monitors below are not intended to intercept CIFS calls.
422 * CIFS higher-level routines will break oplocks as needed prior
423 * to getting to the VFS layer.
424 */
425 static int
smb_fem_oplock_open(femarg_t * arg,int mode,cred_t * cr,caller_context_t * ct)426 smb_fem_oplock_open(
427 femarg_t *arg,
428 int mode,
429 cred_t *cr,
430 caller_context_t *ct)
431 {
432 smb_node_t *node;
433 uint32_t status;
434 int rc = 0;
435
436 if (ct != &smb_ct) {
437 uint32_t req_acc = FILE_READ_DATA;
438 uint32_t cr_disp = FILE_OPEN_IF;
439
440 node = (smb_node_t *)(arg->fa_fnode->fn_available);
441 SMB_NODE_VALID(node);
442
443 /*
444 * Get req_acc, cr_disp just accurate enough so
445 * the oplock break call does the right thing.
446 */
447 if (mode & FWRITE) {
448 req_acc = FILE_READ_DATA | FILE_WRITE_DATA;
449 cr_disp = (mode & FTRUNC) ?
450 FILE_OVERWRITE_IF : FILE_OPEN_IF;
451 } else {
452 req_acc = FILE_READ_DATA;
453 cr_disp = FILE_OPEN_IF;
454 }
455
456 status = smb_oplock_break_OPEN(node, NULL,
457 req_acc, cr_disp);
458 if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS)
459 rc = smb_fem_oplock_wait(node, ct);
460 else if (status != 0)
461 rc = EIO;
462 }
463 if (rc == 0)
464 rc = vnext_open(arg, mode, cr, ct);
465
466 return (rc);
467 }
468
469 /*
470 * Should normally be hit only via NFSv2/v3. All other accesses
471 * (CIFS/NFS/local) should call VOP_OPEN first.
472 */
473
474 static int
smb_fem_oplock_read(femarg_t * arg,uio_t * uiop,int ioflag,cred_t * cr,caller_context_t * ct)475 smb_fem_oplock_read(
476 femarg_t *arg,
477 uio_t *uiop,
478 int ioflag,
479 cred_t *cr,
480 caller_context_t *ct)
481 {
482 smb_node_t *node;
483 uint32_t status;
484 int rc = 0;
485
486 if (ct != &smb_ct) {
487 node = (smb_node_t *)(arg->fa_fnode->fn_available);
488 SMB_NODE_VALID(node);
489
490 status = smb_oplock_break_READ(node, NULL);
491 if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS)
492 rc = smb_fem_oplock_wait(node, ct);
493 else if (status != 0)
494 rc = EIO;
495 }
496 if (rc == 0)
497 rc = vnext_read(arg, uiop, ioflag, cr, ct);
498
499 return (rc);
500 }
501
502 /*
503 * Should normally be hit only via NFSv2/v3. All other accesses
504 * (CIFS/NFS/local) should call VOP_OPEN first.
505 */
506
507 static int
smb_fem_oplock_write(femarg_t * arg,uio_t * uiop,int ioflag,cred_t * cr,caller_context_t * ct)508 smb_fem_oplock_write(
509 femarg_t *arg,
510 uio_t *uiop,
511 int ioflag,
512 cred_t *cr,
513 caller_context_t *ct)
514 {
515 smb_node_t *node;
516 uint32_t status;
517 int rc = 0;
518
519 if (ct != &smb_ct) {
520 node = (smb_node_t *)(arg->fa_fnode->fn_available);
521 SMB_NODE_VALID(node);
522
523 status = smb_oplock_break_WRITE(node, NULL);
524 if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS)
525 rc = smb_fem_oplock_wait(node, ct);
526 else if (status != 0)
527 rc = EIO;
528 }
529 if (rc == 0)
530 rc = vnext_write(arg, uiop, ioflag, cr, ct);
531
532 return (rc);
533 }
534
535 static int
smb_fem_oplock_setattr(femarg_t * arg,vattr_t * vap,int flags,cred_t * cr,caller_context_t * ct)536 smb_fem_oplock_setattr(
537 femarg_t *arg,
538 vattr_t *vap,
539 int flags,
540 cred_t *cr,
541 caller_context_t *ct)
542 {
543 smb_node_t *node;
544 uint32_t status;
545 int rc = 0;
546
547 if (ct != &smb_ct && (vap->va_mask & AT_SIZE) != 0) {
548 node = (smb_node_t *)(arg->fa_fnode->fn_available);
549 SMB_NODE_VALID(node);
550
551 status = smb_oplock_break_SETINFO(node, NULL,
552 FileEndOfFileInformation);
553 if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS)
554 rc = smb_fem_oplock_wait(node, ct);
555 else if (status != 0)
556 rc = EIO;
557 }
558 if (rc == 0)
559 rc = vnext_setattr(arg, vap, flags, cr, ct);
560 return (rc);
561 }
562
563 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)564 smb_fem_oplock_space(
565 femarg_t *arg,
566 int cmd,
567 flock64_t *bfp,
568 int flag,
569 offset_t offset,
570 cred_t *cr,
571 caller_context_t *ct)
572 {
573 smb_node_t *node;
574 uint32_t status;
575 int rc = 0;
576
577 if (ct != &smb_ct) {
578 node = (smb_node_t *)(arg->fa_fnode->fn_available);
579 SMB_NODE_VALID(node);
580
581 status = smb_oplock_break_SETINFO(node, NULL,
582 FileAllocationInformation);
583 if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS)
584 rc = smb_fem_oplock_wait(node, ct);
585 else if (status != 0)
586 rc = EIO;
587 }
588 if (rc == 0)
589 rc = vnext_space(arg, cmd, bfp, flag, offset, cr, ct);
590 return (rc);
591 }
592
593 /*
594 * smb_fem_oplock_vnevent()
595 *
596 * To intercept NFS and local renames and removes in order to break any
597 * existing oplock prior to the operation.
598 *
599 * Note: Currently, this monitor is traversed only when an FS is mounted
600 * non-nbmand. (When the FS is mounted nbmand, share reservation checking
601 * will detect a share violation and return an error prior to the VOP layer
602 * being reached.) Thus, for nbmand NFS and local renames and removes,
603 * an existing oplock is never broken prior to share checking (contrary to
604 * how it is with intra-CIFS remove and rename requests).
605 */
606
607 static int
smb_fem_oplock_vnevent(femarg_t * arg,vnevent_t vnevent,vnode_t * dvp,char * name,caller_context_t * ct)608 smb_fem_oplock_vnevent(
609 femarg_t *arg,
610 vnevent_t vnevent,
611 vnode_t *dvp,
612 char *name,
613 caller_context_t *ct)
614 {
615 smb_node_t *node;
616 uint32_t status;
617 int rc = 0;
618
619 if (ct != &smb_ct) {
620 node = (smb_node_t *)(arg->fa_fnode->fn_available);
621 SMB_NODE_VALID(node);
622
623 switch (vnevent) {
624 case VE_REMOVE:
625 case VE_PRE_RENAME_DEST:
626 case VE_RENAME_DEST:
627 status = smb_oplock_break_HANDLE(node, NULL);
628 break;
629 case VE_PRE_RENAME_SRC:
630 case VE_RENAME_SRC:
631 status = smb_oplock_break_SETINFO(node, NULL,
632 FileRenameInformation);
633 break;
634 default:
635 status = 0;
636 break;
637 }
638 if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS)
639 rc = smb_fem_oplock_wait(node, ct);
640 else if (status != 0)
641 rc = EIO;
642 }
643 if (rc == 0)
644 rc = vnext_vnevent(arg, vnevent, dvp, name, ct);
645
646 return (rc);
647 }
648
649 int smb_fem_oplock_timeout = 5000; /* mSec. */
650
651 static int
smb_fem_oplock_wait(smb_node_t * node,caller_context_t * ct)652 smb_fem_oplock_wait(smb_node_t *node, caller_context_t *ct)
653 {
654 int rc = 0;
655
656 ASSERT(ct != &smb_ct);
657
658 if (ct && (ct->cc_flags & CC_DONTBLOCK)) {
659 ct->cc_flags |= CC_WOULDBLOCK;
660 rc = EAGAIN;
661 } else {
662 (void) smb_oplock_wait_break(node,
663 smb_fem_oplock_timeout);
664 rc = 0;
665 }
666
667 return (rc);
668 }
669