/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * DDI entry points. */ static int smb_drv_attach(dev_info_t *, ddi_attach_cmd_t); static int smb_drv_detach(dev_info_t *, ddi_detach_cmd_t); static int smb_drv_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **); static int smb_drv_open(dev_t *, int, int, cred_t *); static int smb_drv_close(dev_t, int, int, cred_t *); static int smb_drv_busy(void); static int smb_drv_ioctl(dev_t, int, intptr_t, int, cred_t *, int *); /* * module linkage info for the kernel */ static struct cb_ops cbops = { smb_drv_open, /* cb_open */ smb_drv_close, /* cb_close */ nodev, /* cb_strategy */ nodev, /* cb_print */ nodev, /* cb_dump */ nodev, /* cb_read */ nodev, /* cb_write */ smb_drv_ioctl, /* cb_ioctl */ nodev, /* cb_devmap */ nodev, /* cb_mmap */ nodev, /* cb_segmap */ nochpoll, /* cb_chpoll */ ddi_prop_op, /* cb_prop_op */ NULL, /* cb_streamtab */ D_MP, /* cb_flag */ CB_REV, /* cb_rev */ nodev, /* cb_aread */ nodev, /* cb_awrite */ }; static struct dev_ops devops = { DEVO_REV, /* devo_rev */ 0, /* devo_refcnt */ smb_drv_getinfo, /* devo_getinfo */ nulldev, /* devo_identify */ nulldev, /* devo_probe */ smb_drv_attach, /* devo_attach */ smb_drv_detach, /* devo_detach */ nodev, /* devo_reset */ &cbops, /* devo_cb_ops */ NULL, /* devo_bus_ops */ NULL, /* devo_power */ }; static struct modldrv modldrv = { &mod_driverops, /* drv_modops */ "CIFS Server Protocol %I%", /* drv_linkinfo */ &devops, }; static struct modlinkage modlinkage = { MODREV_1, /* revision of the module, must be: MODREV_1 */ &modldrv, /* ptr to linkage structures */ NULL, }; static int smb_info_init(struct smb_info *si); static void smb_info_fini(struct smb_info *si); extern int smb_fsop_start(void); extern void smb_fsop_stop(void); extern int nt_mapk_start(void); extern void nt_mapk_stop(void); extern int smb_get_kconfig(smb_kmod_cfg_t *cfg); extern void smb_notify_change_daemon(smb_thread_t *thread, void *arg); extern void smb_nbt_daemon(smb_thread_t *thread, void *arg); extern void smb_tcp_daemon(smb_thread_t *thread, void *arg); extern void smb_timers(smb_thread_t *thread, void *arg); extern void smb_session_worker(void *arg); extern int smb_maxbufsize; extern time_t smb_oplock_timeout; /* Debug logging level: 0=Disabled, 1=Quiet, 2=Verbose */ int smbsrv_debug_level; struct smb_info smb_info; static dev_info_t *smb_drv_dip = NULL; static kmutex_t smb_drv_opencount_lock; static int smb_drv_opencount = 0; /* * Kstat smb_info statistics. */ static struct smbinfo_stats { kstat_named_t state; kstat_named_t open_files; kstat_named_t open_trees; kstat_named_t open_users; } smbinfo_stats = { { "state", KSTAT_DATA_UINT32 }, { "open_files", KSTAT_DATA_UINT32 }, { "connections", KSTAT_DATA_UINT32 }, { "sessions", KSTAT_DATA_UINT32 } }; static int smb_kstat_init(void); static void smb_kstat_fini(void); static int smb_kstat_update_info(kstat_t *ksp, int rw); extern void smb_initialize_dispatch_kstat(void); extern void smb_remove_dispatch_kstat(void); static kstat_t *smbinfo_ksp = NULL; /* * SMB pseudo-driver entry points */ int _init(void) { int rc; mutex_init(&smb_drv_opencount_lock, NULL, MUTEX_DRIVER, NULL); if ((rc = mod_install(&modlinkage)) != 0) { mutex_destroy(&smb_drv_opencount_lock); cmn_err(CE_NOTE, "init: %d\n", rc); return (rc); } return (0); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } int _fini(void) { int rc; mutex_enter(&smb_drv_opencount_lock); if (smb_drv_busy()) { mutex_exit(&smb_drv_opencount_lock); return (EBUSY); } mutex_exit(&smb_drv_opencount_lock); if ((rc = mod_remove(&modlinkage)) == 0) mutex_destroy(&smb_drv_opencount_lock); return (rc); } /* * DDI entry points. */ /* ARGSUSED */ static int smb_drv_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result) { ulong_t instance = getminor((dev_t)arg); switch (cmd) { case DDI_INFO_DEVT2DEVINFO: *result = smb_drv_dip; return (DDI_SUCCESS); case DDI_INFO_DEVT2INSTANCE: *result = (void *)instance; return (DDI_SUCCESS); default: break; } return (DDI_FAILURE); } static int smb_drv_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { if (cmd != DDI_ATTACH) { return (DDI_FAILURE); } if (ddi_get_instance(dip) != 0) { /* we only allow instance 0 to attach */ return (DDI_FAILURE); } smb_drv_dip = dip; /* create the minor node */ if (ddi_create_minor_node(dip, "smbsrv", S_IFCHR, 0, DDI_PSEUDO, 0) != DDI_SUCCESS) { cmn_err(CE_WARN, "smb_drv_attach: failed creating minor node"); ddi_remove_minor_node(dip, NULL); return (DDI_FAILURE); } if (smb_service_init() != 0) { ddi_remove_minor_node(dip, NULL); cmn_err(CE_WARN, "smb_drv_attach: failed to initialize " "SMB service"); return (DDI_FAILURE); } return (DDI_SUCCESS); } static int smb_drv_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { if (cmd != DDI_DETACH) return (DDI_FAILURE); mutex_enter(&smb_drv_opencount_lock); /* * Service state value is not protected by a lock in this case but * it shouldn't be possible for the service state machine to transition * TO a busy state at a time when smb_drv_busy() would return false. */ if (smb_drv_busy() || smb_svcstate_sm_busy()) { mutex_exit(&smb_drv_opencount_lock); return (DDI_FAILURE); } mutex_exit(&smb_drv_opencount_lock); smb_service_fini(); smb_drv_dip = NULL; ddi_remove_minor_node(dip, NULL); return (DDI_SUCCESS); } /* ARGSUSED */ static int smb_drv_ioctl(dev_t drv, int cmd, intptr_t argp, int flag, cred_t *cred, int *retval) { int gmtoff; switch (cmd) { case SMB_IOC_GMTOFF: if (ddi_copyin((int *)argp, &gmtoff, sizeof (int), flag)) return (EFAULT); (void) smb_set_gmtoff((uint32_t)gmtoff); break; case SMB_IOC_CONFIG_REFRESH: #if 0 smb_svcstate_event(SMB_SVCEVT_CONFIG, NULL); #endif break; default: break; } return (0); } /* ARGSUSED */ static int smb_drv_open(dev_t *devp, int flag, int otyp, cred_t *credp) { int rc = 0; /* * Only allow one open at a time */ mutex_enter(&smb_drv_opencount_lock); if (smb_drv_busy()) { mutex_exit(&smb_drv_opencount_lock); return (EBUSY); } smb_drv_opencount++; mutex_exit(&smb_drv_opencount_lock); /* * Check caller's privileges. */ if (secpolicy_smb(credp) != 0) { mutex_enter(&smb_drv_opencount_lock); smb_drv_opencount--; mutex_exit(&smb_drv_opencount_lock); return (EPERM); } /* * Start SMB service state machine */ rc = smb_svcstate_sm_start(&smb_info.si_svc_sm_ctx); if (rc != 0) { mutex_enter(&smb_drv_opencount_lock); smb_drv_opencount--; mutex_exit(&smb_drv_opencount_lock); return (rc); } return (0); } /* ARGSUSED */ static int smb_drv_close(dev_t dev, int flag, int otyp, cred_t *credp) { mutex_enter(&smb_drv_opencount_lock); if (!smb_drv_busy()) { mutex_exit(&smb_drv_opencount_lock); return (0); } mutex_exit(&smb_drv_opencount_lock); smb_svcstate_event(SMB_SVCEVT_CLOSE, NULL); mutex_enter(&smb_drv_opencount_lock); smb_drv_opencount--; mutex_exit(&smb_drv_opencount_lock); return (0); } /* * Convenience function - must be called with smb_drv_opencount_lock held. */ static int smb_drv_busy(void) { ASSERT(mutex_owned(&smb_drv_opencount_lock)); return (smb_drv_opencount); } /* * SMB Service initialization and startup functions */ int smb_service_init(void) { int rc; rc = smb_info_init(&smb_info); if (rc != 0) { return (rc); } rc = smb_svcstate_sm_init(&smb_info.si_svc_sm_ctx); if (rc != 0) { smb_info_fini(&smb_info); return (rc); } rc = smb_kstat_init(); if (rc != 0) { smb_kstat_fini(); return (rc); } smb_winpipe_init(); return (0); } void smb_service_fini(void) { smb_winpipe_fini(); smb_kstat_fini(); smb_svcstate_sm_fini(&smb_info.si_svc_sm_ctx); smb_info_fini(&smb_info); } /* * Progress bits for smb_info.si_open_progress. For use only by * smb_service_open/smb_service_close. */ #define SMB_FS_STARTED 0x01 #define LMSHRD_KCLIENT_STARTED 0x02 #define SMB_KDOOR_CLNT_STARTED 0x04 #define SMB_KDOOR_SRV_STARTED 0x08 #define SMB_THREADS_STARTED 0x10 int smb_service_open(struct smb_info *si) { int rc; int size; /* XXX TEMPORARY (remove when kconfig is removed) */ /* Track progress so we can cleanup from a partial failure */ si->si_open_progress = 0; si->si_connect_progress = 0; /* XXX TEMPORARY */ if (smb_get_kconfig(&si->si) == 0) { if (si->si.skc_sync_enable) smb_set_stability(1); if (si->si.skc_flush_required) smb_commit_required(0); if (si->si.skc_maxconnections == 0) si->si.skc_maxconnections = 0xFFFFFFFF; size = si->si.skc_maxbufsize; if (size != 0) { if (size < 37 || size > 64) size = 37; smb_maxbufsize = SMB_NT_MAXBUF(size); } /* * XXX should not override configuration. * For now, this disables server side * signing regardless of configuration. */ si->si.skc_signing_enable = 0; si->si.skc_signing_required = 0; si->si.skc_signing_check = 0; smb_correct_keep_alive_values(si->si.skc_keepalive); /* * XXX The following code was pulled from smb_oplock_init. * It should be combined with with the config process if * this info will be stored with the configuration or with * the smb_fsop_start function if the data will be stored * in the root of the fs. */ /* * XXX oplock enable flag. * Should be stored in extended attribute in root of fs * or a ZFS user-defined property. */ if (si->si.skc_oplock_enable == 0) { cmn_err(CE_NOTE, "SmbOplocks: disabled"); } smb_oplock_timeout = si->si.skc_oplock_timeout; /* * XXX oplock timeout. Can a customer configure this? */ if (si->si.skc_oplock_timeout < OPLOCK_MIN_TIMEOUT) smb_oplock_timeout = OPLOCK_MIN_TIMEOUT; } else { return (EIO); /* XXX Errno? */ } if ((rc = smb_fsop_start()) != 0) { return (rc); } si->si_open_progress |= SMB_FS_STARTED; if ((rc = lmshrd_kclient_start()) != 0) { return (rc); } si->si_open_progress |= LMSHRD_KCLIENT_STARTED; if ((rc = smb_kdoor_clnt_start()) != 0) { return (rc); } si->si_open_progress |= SMB_KDOOR_CLNT_STARTED; if ((rc = smb_kdoor_srv_start()) != 0) { return (rc); } si->si_open_progress |= SMB_KDOOR_SRV_STARTED; if ((rc = smb_service_start_threads(si)) != 0) { return (rc); } si->si_open_progress |= SMB_THREADS_STARTED; return (0); } void smb_service_close(struct smb_info *si) { if (si->si_open_progress & SMB_THREADS_STARTED) smb_service_stop_threads(si); if (si->si_open_progress & SMB_KDOOR_SRV_STARTED) smb_kdoor_srv_stop(); if (si->si_open_progress & SMB_KDOOR_CLNT_STARTED) smb_kdoor_clnt_stop(); if (si->si_open_progress & LMSHRD_KCLIENT_STARTED) lmshrd_kclient_stop(); if (si->si_open_progress & SMB_FS_STARTED) smb_fsop_stop(); } /* * Start the Netbios and TCP services. * * Awaken arguments are not known until thread starts. * * XXX We give up the NET_MAC_AWARE privilege because it keeps us from * re-opening the connection when there are leftover TCP connections in * TCPS_TIME_WAIT state. There seem to be some security ramifications * around reestablishing a connection while possessing the NET_MAC_AWARE * privilege. * * This approach may cause problems when we try to support zones. An * alternative would be to retry the connection setup for a fixed period * of time until the stale connections clear up but that implies we * would be offline for a couple minutes every time the service is * restarted with active connections. */ int smb_service_connect(struct smb_info *si) { int rc1, rc2; if ((rc1 = setpflags(NET_MAC_AWARE, 0, CRED())) != 0) { cmn_err(CE_WARN, "Cannot remove NET_MAC_AWARE privilege"); smb_svcstate_event(SMB_SVCEVT_DISCONNECT, (uintptr_t)rc1); return (rc1); } rc1 = smb_thread_start(&si->si_nbt_daemon); rc2 = smb_thread_start(&si->si_tcp_daemon); if (rc2 != 0) rc1 = rc2; return (rc1); } void smb_service_disconnect(struct smb_info *si) { smb_thread_stop(&si->si_nbt_daemon); smb_thread_stop(&si->si_tcp_daemon); } /* * Start any service-related kernel threads except for the NBT and TCP * daemon threads. Those service daemon threads are handled separately. * * Returns 0 for success, non-zero for failure. If failure is returned the * caller should call smb_service_stop_threads to cleanup any threads that * were successfully started. */ int smb_service_start_threads(struct smb_info *si) { int rval; si->thread_pool = taskq_create( "smb_workers", si->si.skc_maxworkers, SMB_WORKER_PRIORITY, si->si.skc_maxworkers, INT_MAX, TASKQ_DYNAMIC|TASKQ_PREPOPULATE); ASSERT(si->thread_pool != NULL); rval = smb_thread_start(&si->si_thread_notify_change); if (rval != 0) return (rval); rval = smb_thread_start(&si->si_thread_timers); if (rval != 0) { smb_thread_stop(&si->si_thread_notify_change); return (rval); } return (0); } void smb_service_stop_threads(struct smb_info *si) { smb_thread_stop(&si->si_thread_timers); smb_thread_stop(&si->si_thread_notify_change); taskq_destroy(si->thread_pool); } static int smb_info_init(struct smb_info *si) { int i; bzero(si, sizeof (smb_info)); for (i = 0; i <= SMBND_HASH_MASK; i++) { smb_llist_constructor(&si->node_hash_table[i], sizeof (smb_node_t), offsetof(smb_node_t, n_lnd)); } smb_llist_constructor(&si->si_vfs_list, sizeof (smb_vfs_t), offsetof(smb_vfs_t, sv_lnd)); smb_slist_constructor(&si->si_ncr_list, sizeof (smb_request_t), offsetof(smb_request_t, sr_ncr.nc_lnd)); smb_slist_constructor(&si->si_nce_list, sizeof (smb_request_t), offsetof(smb_request_t, sr_ncr.nc_lnd)); si->si_cache_vfs = kmem_cache_create("smb_vfs_cache", sizeof (smb_vfs_t), 8, NULL, NULL, NULL, NULL, NULL, 0); si->si_cache_request = kmem_cache_create("smb_request_cache", sizeof (smb_request_t), 8, NULL, NULL, NULL, NULL, NULL, 0); si->si_cache_session = kmem_cache_create("smb_session_cache", sizeof (smb_session_t), 8, NULL, NULL, NULL, NULL, NULL, 0); si->si_cache_user = kmem_cache_create("smb_user_cache", sizeof (smb_user_t), 8, NULL, NULL, NULL, NULL, NULL, 0); si->si_cache_tree = kmem_cache_create("smb_tree_cache", sizeof (smb_tree_t), 8, NULL, NULL, NULL, NULL, NULL, 0); si->si_cache_ofile = kmem_cache_create("smb_ofile_cache", sizeof (smb_ofile_t), 8, NULL, NULL, NULL, NULL, NULL, 0); si->si_cache_odir = kmem_cache_create("smb_odir_cache", sizeof (smb_odir_t), 8, NULL, NULL, NULL, NULL, NULL, 0); si->si_cache_node = kmem_cache_create("smb_smb_node_cache", sizeof (smb_node_t), 8, NULL, NULL, NULL, NULL, NULL, 0); smb_thread_init(&si->si_nbt_daemon, "smb_nbt_daemon", smb_nbt_daemon, si, NULL, NULL); smb_thread_init(&si->si_tcp_daemon, "smb_tcp_daemon", smb_tcp_daemon, si, NULL, NULL); smb_thread_init(&si->si_thread_notify_change, "smb_notify_change_daemon", smb_notify_change_daemon, &smb_info, NULL, NULL); smb_thread_init(&si->si_thread_timers, "smb_timers", smb_timers, si, NULL, NULL); return (0); } static void smb_info_fini(struct smb_info *si) { int i; for (i = 0; i <= SMBND_HASH_MASK; i++) { smb_node_t *node; /* * The following sequence is just intended for sanity check. * This will have to be modified when the code goes into * production. * * The SMB node hash table should be emtpy at this point. If the * hash table is not empty all the nodes remaining are displayed * (it should help figure out what actions led to this state) * and "oops" will be set to B_TRUE which will trigger the * ASSERT that follows. * * The reason why SMB nodes are still remaining in the hash * table is problably due to a mismatch between calls to * smb_node_lookup() and smb_node_release(). You must track that * down. * * Now if you are reading this comment because you actually hit * the ASSERT, the temptation to ignore it is going to be very * strong. To help you make the right decision you should know * that when the ASSERT happened a message containing you SunID * has been sent to cifsgate. By now it has been logged into a * special database. * * You are being watched... */ node = smb_llist_head(&si->node_hash_table[i]); ASSERT(node == NULL); } for (i = 0; i <= SMBND_HASH_MASK; i++) { smb_llist_destructor(&si->node_hash_table[i]); } smb_llist_destructor(&si->si_vfs_list); kmem_cache_destroy(si->si_cache_vfs); kmem_cache_destroy(si->si_cache_request); kmem_cache_destroy(si->si_cache_session); kmem_cache_destroy(si->si_cache_user); kmem_cache_destroy(si->si_cache_tree); kmem_cache_destroy(si->si_cache_ofile); kmem_cache_destroy(si->si_cache_odir); kmem_cache_destroy(si->si_cache_node); smb_thread_destroy(&si->si_nbt_daemon); smb_thread_destroy(&si->si_tcp_daemon); smb_thread_destroy(&si->si_thread_notify_change); smb_thread_destroy(&si->si_thread_timers); } static int smb_kstat_init() { /* create and initialize smb kstats - smb_info stats */ smbinfo_ksp = kstat_create("smb", 0, "smb_info", "misc", KSTAT_TYPE_NAMED, sizeof (smbinfo_stats) / sizeof (kstat_named_t), KSTAT_FLAG_VIRTUAL); if (smbinfo_ksp) { smbinfo_ksp->ks_data = (void *) &smbinfo_stats; smbinfo_ksp->ks_update = smb_kstat_update_info; kstat_install(smbinfo_ksp); } /* create and initialize smb kstats - smb_dispatch stats */ smb_initialize_dispatch_kstat(); return (0); } static void smb_kstat_fini() { if (smbinfo_ksp != NULL) { kstat_delete(smbinfo_ksp); smbinfo_ksp = NULL; } smb_remove_dispatch_kstat(); } /* ARGSUSED */ static int smb_kstat_update_info(kstat_t *ksp, int rw) { if (rw == KSTAT_WRITE) { return (EACCES); } else { smbinfo_stats.state.value.ui32 = smb_info.si_svc_sm_ctx.ssc_state; smbinfo_stats.open_files.value.ui32 = smb_info.open_files; smbinfo_stats.open_trees.value.ui32 = smb_info.open_trees; smbinfo_stats.open_users.value.ui32 = smb_info.open_users; } return (0); }