Home | History | Annotate | Download | only in smbsrv
      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 2009 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 
     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 void 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_setsecattr(femarg_t *, vsecattr_t *, int, cred_t *,
     93     caller_context_t *);
     94 static int smb_fem_oplock_vnevent(femarg_t *, vnevent_t, vnode_t *, char *,
     95     caller_context_t *);
     96 
     97 static 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 
    109 static 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  */
    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 static int
    401 smb_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 
    420 static int
    421 smb_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 
    441 static int
    442 smb_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 
    457 static int
    458 smb_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 
    473 static int
    474 smb_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 
    489 static int
    490 smb_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 
    507 static int
    508 smb_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 
    537 static int
    538 smb_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 
    562 static int
    563 smb_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