usr/src/lib/smbsrv/libmlsvc/common/smb_share.c
changeset 7961 4b5e3051f38b
parent 7588 fc605a2defdc
child 8334 5f1c6a3b0fad
--- a/usr/src/lib/smbsrv/libmlsvc/common/smb_share.c	Mon Oct 27 23:12:59 2008 -0700
+++ b/usr/src/lib/smbsrv/libmlsvc/common/smb_share.c	Tue Oct 28 03:34:04 2008 -0700
@@ -24,73 +24,89 @@
  */
 
 /*
- * Lan Manager (SMB/CIFS) share interface implementation. This interface
- * returns Win32 error codes, usually network error values (lmerr.h).
+ * SMB/CIFS share cache implementation.
  */
 
 #include <errno.h>
 #include <synch.h>
-#include <stdio.h>
 #include <stdlib.h>
 #include <strings.h>
 #include <syslog.h>
 #include <thread.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <netdb.h>
-#include <synch.h>
 #include <pthread.h>
-#include <ctype.h>
 #include <assert.h>
-#include <sys/mnttab.h>
-#include <sys/stat.h>
-#include <sys/types.h>
+#include <libshare.h>
 
 #include <smbsrv/libsmb.h>
 #include <smbsrv/libsmbns.h>
 #include <smbsrv/libmlsvc.h>
 
-#include <libshare.h>
-
 #include <smbsrv/lm.h>
 #include <smbsrv/smb_share.h>
 #include <smbsrv/cifs.h>
+#include <smbsrv/nterror.h>
 
-#include <smbsrv/ctype.h>
-#include <smbsrv/smb_vops.h>
-#include <smbsrv/nterror.h>
+#define	SMB_SHR_ERROR_THRESHOLD		3
 
 /*
  * Cache functions and vars
  */
-#define	SMB_SHARE_HTAB_SZ	1024
+#define	SMB_SHR_HTAB_SZ			1024
 
-static HT_HANDLE *smb_shr_handle = NULL;
-static rwlock_t smb_shr_lock;
-static pthread_t smb_shr_populate_thr;
+/*
+ * Cache handle
+ *
+ * Shares cache is a hash table.
+ *
+ * sc_cache		pointer to hash table handle
+ * sc_cache_lck		synchronize cache read/write accesses
+ * sc_state		cache state machine values
+ * sc_nops		number of inflight/pending cache operations
+ * sc_mtx		protects handle fields
+ */
+typedef struct smb_shr_cache {
+	HT_HANDLE	*sc_cache;
+	rwlock_t	sc_cache_lck;
+	mutex_t		sc_mtx;
+	cond_t		sc_cv;
+	uint32_t	sc_state;
+	uint32_t	sc_nops;
+} smb_shr_cache_t;
+
+/*
+ * Cache states
+ */
+#define	SMB_SHR_CACHE_STATE_NONE	0
+#define	SMB_SHR_CACHE_STATE_CREATED	1
+#define	SMB_SHR_CACHE_STATE_DESTROYING	2
+
+/*
+ * Cache lock modes
+ */
+#define	SMB_SHR_CACHE_RDLOCK	0
+#define	SMB_SHR_CACHE_WRLOCK	1
+
+static smb_shr_cache_t smb_shr_cache;
 
 static uint32_t smb_shr_cache_create(void);
 static void smb_shr_cache_destroy(void);
-static void *smb_shr_cache_populate(void *);
+static uint32_t smb_shr_cache_lock(int);
+static void smb_shr_cache_unlock(void);
+static int smb_shr_cache_count(void);
+static smb_share_t *smb_shr_cache_iterate(smb_shriter_t *);
+
+static smb_share_t *smb_shr_cache_findent(char *);
 static uint32_t smb_shr_cache_addent(smb_share_t *);
 static void smb_shr_cache_delent(char *);
-static uint32_t smb_shr_cache_chgent(smb_share_t *);
 static void smb_shr_cache_freent(HT_ITEM *);
-static uint32_t smb_shr_cache_loadent(sa_share_t, sa_resource_t);
-static void smb_shr_cache_loadgrp(sa_group_t);
-
-static void smb_shr_set_ahcnt(char *, int);
-static void smb_shr_set_oemname(smb_share_t *);
-static uint32_t smb_shr_create_autohome(smb_share_t *);
-static uint32_t smb_shr_create_ipc(void);
 
 /*
  * sharemgr functions
  */
-static uint32_t smb_shr_sa_delent(smb_share_t *);
-static uint32_t smb_shr_sa_addent(smb_share_t *);
-static uint32_t smb_shr_sa_getent(sa_share_t, sa_resource_t, smb_share_t *);
-static sa_group_t smb_shr_sa_getdefgrp(sa_handle_t);
+static void *smb_shr_sa_loadall(void *);
+static void smb_shr_sa_loadgrp(sa_group_t);
+static uint32_t smb_shr_sa_load(sa_share_t, sa_resource_t);
+static uint32_t smb_shr_sa_get(sa_share_t, sa_resource_t, smb_share_t *);
 
 /*
  * share publishing
@@ -117,7 +133,6 @@
  * share publishing queue
  */
 typedef struct smb_shr_pqueue {
-	int		spq_cnt;
 	list_t		spq_list;
 	mutex_t		spq_mtx;
 	cond_t		spq_cv;
@@ -125,18 +140,23 @@
 } smb_shr_pqueue_t;
 
 static smb_shr_pqueue_t ad_queue;
-static pthread_t smb_shr_publish_thr;
 
 static int smb_shr_publisher_start(void);
 static void smb_shr_publisher_stop(void);
 static void smb_shr_publisher_send(smb_ads_handle_t *, list_t *, const char *);
+static void smb_shr_publisher_queue(const char *, const char *, char);
 static void *smb_shr_publisher(void *);
-static void smb_shr_publish(const char *, const char *, char);
-
+static void smb_shr_publisher_flush(list_t *);
+static void smb_shr_publish(const char *, const char *);
+static void smb_shr_unpublish(const char *, const char *);
 
 /*
- * smb_shr_start
- *
+ * Utility/helper functions
+ */
+static uint32_t smb_shr_addipc(void);
+static void smb_shr_set_oemname(smb_share_t *);
+
+/*
  * Starts the publisher thread and another thread which
  * populates the share cache by share information stored
  * by sharemgr
@@ -144,16 +164,22 @@
 int
 smb_shr_start(void)
 {
+	pthread_t load_thr;
 	pthread_attr_t tattr;
 	int rc;
 
 	if ((rc = smb_shr_publisher_start()) != 0)
 		return (rc);
 
+	if (smb_shr_cache_create() != NERR_Success)
+		return (ENOMEM);
+
+	if (smb_shr_addipc() != NERR_Success)
+		return (ENOMEM);
+
 	(void) pthread_attr_init(&tattr);
 	(void) pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
-	rc = pthread_create(&smb_shr_populate_thr, &tattr,
-	    smb_shr_cache_populate, 0);
+	rc = pthread_create(&load_thr, &tattr, smb_shr_sa_loadall, 0);
 	(void) pthread_attr_destroy(&tattr);
 
 	return (rc);
@@ -167,18 +193,17 @@
 }
 
 /*
- * smb_shr_count
- *
  * Return the total number of shares
  */
 int
 smb_shr_count(void)
 {
-	int n_shares;
+	int n_shares = 0;
 
-	(void) rw_rdlock(&smb_shr_lock);
-	n_shares = ht_get_total_items(smb_shr_handle);
-	(void) rw_unlock(&smb_shr_lock);
+	if (smb_shr_cache_lock(SMB_SHR_CACHE_RDLOCK) == NERR_Success) {
+		n_shares = smb_shr_cache_count();
+		smb_shr_cache_unlock();
+	}
 
 	return (n_shares);
 }
@@ -208,39 +233,39 @@
 smb_share_t *
 smb_shr_iterate(smb_shriter_t *shi)
 {
-	HT_ITEM *item;
 	smb_share_t *share = NULL;
+	smb_share_t *cached_si;
 
-	if (smb_shr_handle == NULL || shi == NULL)
+	if (shi == NULL)
 		return (NULL);
 
-	(void) rw_rdlock(&smb_shr_lock);
-	if (shi->si_first) {
-		item = ht_findfirst(smb_shr_handle, &shi->si_hashiter);
-		shi->si_first = B_FALSE;
-	} else {
-		item = ht_findnext(&shi->si_hashiter);
+	if (smb_shr_cache_lock(SMB_SHR_CACHE_RDLOCK) == NERR_Success) {
+		if ((cached_si = smb_shr_cache_iterate(shi)) != NULL) {
+			share = &shi->si_share;
+			bcopy(cached_si, share, sizeof (smb_share_t));
+		}
+		smb_shr_cache_unlock();
 	}
 
-	if (item && item->hi_data) {
-		share = &shi->si_share;
-		bcopy(item->hi_data, share, sizeof (smb_share_t));
-	}
-	(void) rw_unlock(&smb_shr_lock);
-
 	return (share);
 }
 
 /*
- * smb_shr_create
+ * Adds the given share to cache, publishes the share in ADS
+ * if it has an AD container, calls kernel to take a hold on
+ * the shared file system. If it can't take a hold on the
+ * shared file system, it's either because shared directory
+ * does not exist or some other error has occurred, in any
+ * case the share is removed from the cache.
  *
- * Adds the given to cache and if 'store' is B_TRUE it's also
- * added to sharemgr
+ * If the specified share is an autohome share which already
+ * exists in the cache, just increments the reference count.
  */
 uint32_t
-smb_shr_create(smb_share_t *si, boolean_t store)
+smb_shr_add(smb_share_t *si)
 {
-	uint32_t status = NERR_Success;
+	smb_share_t *cached_si;
+	uint32_t status;
 	int rc;
 
 	assert(si != NULL);
@@ -248,33 +273,41 @@
 	if (!smb_shr_chkname(si->shr_name))
 		return (ERROR_INVALID_NAME);
 
-	if (si->shr_flags & SMB_SHRF_AUTOHOME)
-		return (smb_shr_create_autohome(si));
-
-	if (smb_shr_exists(si->shr_name))
-		return (NERR_DuplicateShare);
+	if (smb_shr_cache_lock(SMB_SHR_CACHE_WRLOCK) != NERR_Success)
+		return (NERR_InternalError);
 
-	if ((status = smb_shr_cache_addent(si)) != NERR_Success)
+	cached_si = smb_shr_cache_findent(si->shr_name);
+	if (cached_si) {
+		if (si->shr_flags & SMB_SHRF_AUTOHOME) {
+			cached_si->shr_refcnt++;
+			status = NERR_Success;
+		} else {
+			status = NERR_DuplicateShare;
+		}
+		smb_shr_cache_unlock();
 		return (status);
-
-	if (store && (si->shr_flags & SMB_SHRF_PERM)) {
-		if ((status = smb_shr_sa_addent(si)) != NERR_Success) {
-			(void) smb_shr_cache_delent(si->shr_name);
-			return (status);
-		}
 	}
 
+	if ((status = smb_shr_cache_addent(si)) != NERR_Success) {
+		smb_shr_cache_unlock();
+		return (status);
+	}
+
+	/* don't hold the lock across door call */
+	smb_shr_cache_unlock();
+
+	/* call kernel to take a hold on the shared file system */
 	rc = mlsvc_set_share(SMB_SHROP_ADD, si->shr_path, si->shr_name);
 
 	if (rc == 0) {
-		smb_shr_publish(si->shr_name, si->shr_container,
-		    SMB_SHR_PUBLISH);
-		return (status);
+		smb_shr_publish(si->shr_name, si->shr_container);
+		return (NERR_Success);
 	}
 
-	smb_shr_cache_delent(si->shr_name);
-	if (store && (si->shr_flags & SMB_SHRF_PERM))
-		(void) smb_shr_sa_delent(si);
+	if (smb_shr_cache_lock(SMB_SHR_CACHE_WRLOCK) == NERR_Success) {
+		smb_shr_cache_delent(si->shr_name);
+		smb_shr_cache_unlock();
+	}
 
 	/*
 	 * rc == ENOENT means the shared directory doesn't exist
@@ -283,47 +316,60 @@
 }
 
 /*
- * smb_shr_delete
+ * Removes the specified share from cache, removes it from AD
+ * if it has an AD container, and calls the kernel to release
+ * the hold on the shared file system.
  *
- * Removes the specified share.
+ * If this is an autohome share then decrement the reference
+ * count. If it reaches 0 then it proceeds with removing steps.
  */
 uint32_t
-smb_shr_delete(char *sharename, boolean_t store)
+smb_shr_remove(char *sharename)
 {
-	smb_share_t si;
-	uint32_t status = NERR_Success;
+	smb_share_t *si;
+	char path[MAXPATHLEN];
+	char container[MAXPATHLEN];
 
 	assert(sharename != NULL);
 
-	if ((status = smb_shr_get(sharename, &si)) != NERR_Success)
-		return (status);
+	if (!smb_shr_chkname(sharename))
+		return (ERROR_INVALID_NAME);
 
-	if (si.shr_type & STYPE_IPC)
-		return (ERROR_ACCESS_DENIED);
+	if (smb_shr_cache_lock(SMB_SHR_CACHE_WRLOCK) != NERR_Success)
+		return (NERR_InternalError);
+
+	if ((si = smb_shr_cache_findent(sharename)) == NULL) {
+		smb_shr_cache_unlock();
+		return (NERR_NetNameNotFound);
+	}
 
-	if (si.shr_flags & SMB_SHRF_AUTOHOME) {
-		si.shr_refcnt--;
-		if (si.shr_refcnt > 0) {
-			smb_shr_set_ahcnt(si.shr_name, si.shr_refcnt);
-			return (status);
+	if (si->shr_type & STYPE_IPC) {
+		/* IPC$ share cannot be removed */
+		smb_shr_cache_unlock();
+		return (ERROR_ACCESS_DENIED);
+	}
+
+	if (si->shr_flags & SMB_SHRF_AUTOHOME) {
+		if ((--si->shr_refcnt) > 0) {
+			smb_shr_cache_unlock();
+			return (NERR_Success);
 		}
 	}
 
-	if (store && (si.shr_flags & SMB_SHRF_PERM)) {
-		if (smb_shr_sa_delent(&si) != NERR_Success)
-			return (NERR_InternalError);
-	}
+	(void) strlcpy(path, si->shr_path, sizeof (path));
+	(void) strlcpy(container, si->shr_container, sizeof (container));
+	smb_shr_cache_delent(sharename);
+	smb_shr_cache_unlock();
 
-	smb_shr_cache_delent(si.shr_name);
-	smb_shr_publish(si.shr_name, si.shr_container, SMB_SHR_UNPUBLISH);
-	(void) mlsvc_set_share(SMB_SHROP_DELETE, si.shr_path, si.shr_name);
+	smb_shr_unpublish(sharename, container);
+
+	/* call kernel to release the hold on the shared file system */
+	(void) mlsvc_set_share(SMB_SHROP_DELETE, path, sharename);
 
 	return (NERR_Success);
 }
 
 /*
- * smb_shr_rename
- *
  * Rename a share. Check that the current name exists and the new name
  * doesn't exist. The rename is performed by deleting the current share
  * definition and creating a new share with the new name.
@@ -331,7 +377,8 @@
 uint32_t
 smb_shr_rename(char *from_name, char *to_name)
 {
-	smb_share_t si;
+	smb_share_t *from_si;
+	smb_share_t to_si;
 	uint32_t status;
 
 	assert((from_name != NULL) && (to_name != NULL));
@@ -339,149 +386,137 @@
 	if (!smb_shr_chkname(from_name) || !smb_shr_chkname(to_name))
 		return (ERROR_INVALID_NAME);
 
-	if (!smb_shr_exists(from_name))
+	if (smb_shr_cache_lock(SMB_SHR_CACHE_WRLOCK) != NERR_Success)
+		return (NERR_InternalError);
+
+	if ((from_si = smb_shr_cache_findent(from_name)) == NULL) {
+		smb_shr_cache_unlock();
 		return (NERR_NetNameNotFound);
+	}
 
-	if (smb_shr_exists(to_name))
+	if (from_si->shr_type & STYPE_IPC) {
+		/* IPC$ share cannot be renamed */
+		smb_shr_cache_unlock();
+		return (ERROR_ACCESS_DENIED);
+	}
+
+	if (smb_shr_cache_findent(to_name) != NULL) {
+		smb_shr_cache_unlock();
 		return (NERR_DuplicateShare);
+	}
 
-	if ((status = smb_shr_get(from_name, &si)) != NERR_Success)
+	bcopy(from_si, &to_si, sizeof (smb_share_t));
+	(void) strlcpy(to_si.shr_name, to_name, sizeof (to_si.shr_name));
+
+	if ((status = smb_shr_cache_addent(&to_si)) != NERR_Success) {
+		smb_shr_cache_unlock();
 		return (status);
-
-	if (si.shr_type & STYPE_IPC)
-		return (ERROR_ACCESS_DENIED);
-
-	(void) strlcpy(si.shr_name, to_name, sizeof (si.shr_name));
-	if ((status = smb_shr_cache_addent(&si)) != NERR_Success)
-		return (status);
+	}
 
 	smb_shr_cache_delent(from_name);
-	smb_shr_publish(from_name, si.shr_container, SMB_SHR_UNPUBLISH);
-	smb_shr_publish(to_name, si.shr_container, SMB_SHR_PUBLISH);
+	smb_shr_cache_unlock();
+
+	smb_shr_unpublish(from_name, to_si.shr_container);
+	smb_shr_publish(to_name, to_si.shr_container);
 
 	return (NERR_Success);
 }
 
 /*
- * smb_shr_get
- *
  * Load the information for the specified share into the supplied share
  * info structure.
  */
 uint32_t
 smb_shr_get(char *sharename, smb_share_t *si)
 {
-	HT_ITEM *item;
+	smb_share_t *cached_si;
+	uint32_t status = NERR_NetNameNotFound;
 
-	(void) utf8_strlwr(sharename);
+	if (sharename == NULL || *sharename == '\0')
+		return (NERR_NetNameNotFound);
 
-	(void) rw_rdlock(&smb_shr_lock);
-	item = ht_find_item(smb_shr_handle, sharename);
-	if (item == NULL || item->hi_data == NULL) {
-		(void) rw_unlock(&smb_shr_lock);
-		return (NERR_NetNameNotFound);
+	if (smb_shr_cache_lock(SMB_SHR_CACHE_RDLOCK) == NERR_Success) {
+		cached_si = smb_shr_cache_findent(sharename);
+		if (cached_si != NULL) {
+			bcopy(cached_si, si, sizeof (smb_share_t));
+			status = NERR_Success;
+		}
+
+		smb_shr_cache_unlock();
 	}
 
-	bcopy(item->hi_data, si, sizeof (smb_share_t));
-	(void) rw_unlock(&smb_shr_lock);
-
-	return (NERR_Success);
+	return (status);
 }
 
 /*
- * smb_shr_modify
- *
  * Modifies an existing share. Properties that can be modified are:
  *
  *   o comment
  *   o AD container
+ *   o host access
  */
 uint32_t
-smb_shr_modify(char *sharename, const char *cmnt,
-    const char *ad_container, boolean_t store)
+smb_shr_modify(smb_share_t *new_si)
 {
-	smb_share_t si;
-	uint32_t status;
-	boolean_t cmnt_changed = B_FALSE;
+	smb_share_t *si;
 	boolean_t adc_changed = B_FALSE;
-	char shr_container[MAXPATHLEN];
+	char old_container[MAXPATHLEN];
+	uint32_t access;
 
-	assert(sharename != NULL);
+	assert(new_si != NULL);
 
-	if ((cmnt == NULL) && (ad_container == NULL))
-		/* no changes */
-		return (NERR_Success);
-
-	if ((status = smb_shr_get(sharename, &si)) != NERR_Success)
-		return (status);
+	if (smb_shr_cache_lock(SMB_SHR_CACHE_WRLOCK) != NERR_Success)
+		return (NERR_InternalError);
 
-	if (si.shr_type & STYPE_IPC)
-		return (ERROR_ACCESS_DENIED);
+	if ((si = smb_shr_cache_findent(new_si->shr_name)) == NULL) {
+		smb_shr_cache_unlock();
+		return (NERR_NetNameNotFound);
+	}
 
-	if (cmnt) {
-		cmnt_changed = (strcmp(cmnt, si.shr_cmnt) != 0);
-		if (cmnt_changed)
-			(void) strlcpy(si.shr_cmnt, cmnt, sizeof (si.shr_cmnt));
+	if (si->shr_type & STYPE_IPC) {
+		/* IPC$ share cannot be modified */
+		smb_shr_cache_unlock();
+		return (ERROR_ACCESS_DENIED);
 	}
 
-	if (ad_container) {
-		adc_changed = (strcmp(ad_container, si.shr_container) != 0);
-		if (adc_changed) {
-			/* save current container needed for unpublishing */
-			(void) strlcpy(shr_container, si.shr_container,
-			    sizeof (shr_container));
-			(void) strlcpy(si.shr_container, ad_container,
-			    sizeof (si.shr_container));
-		}
+	if (strcmp(new_si->shr_cmnt, si->shr_cmnt) != 0)
+		(void) strlcpy(si->shr_cmnt, new_si->shr_cmnt,
+		    sizeof (si->shr_cmnt));
+
+	adc_changed = (strcmp(new_si->shr_container, si->shr_container) != 0);
+	if (adc_changed) {
+		/* save current container - needed for unpublishing */
+		(void) strlcpy(old_container, si->shr_container,
+		    sizeof (old_container));
+		(void) strlcpy(si->shr_container, new_si->shr_container,
+		    sizeof (si->shr_container));
 	}
 
-	if (!cmnt_changed && !adc_changed)
-		/* no changes */
-		return (NERR_Success);
+	access = (new_si->shr_flags & SMB_SHRF_ACC_ALL);
+	si->shr_flags |= access;
+
+	if (access & SMB_SHRF_ACC_NONE)
+		(void) strlcpy(si->shr_access_none, new_si->shr_access_none,
+		    sizeof (si->shr_access_none));
 
-	if (store && (si.shr_flags & SMB_SHRF_PERM)) {
-		if (smb_shr_sa_addent(&si) != NERR_Success)
-			return (NERR_InternalError);
-	}
+	if (access & SMB_SHRF_ACC_RO)
+		(void) strlcpy(si->shr_access_ro, new_si->shr_access_ro,
+		    sizeof (si->shr_access_ro));
 
-	(void) smb_shr_cache_chgent(&si);
+	if (access & SMB_SHRF_ACC_RW)
+		(void) strlcpy(si->shr_access_rw, new_si->shr_access_rw,
+		    sizeof (si->shr_access_rw));
+
+	smb_shr_cache_unlock();
 
 	if (adc_changed) {
-		smb_shr_publish(si.shr_name, shr_container,
-		    SMB_SHR_UNPUBLISH);
-		smb_shr_publish(si.shr_name, si.shr_container,
-		    SMB_SHR_PUBLISH);
+		smb_shr_unpublish(new_si->shr_name, old_container);
+		smb_shr_publish(new_si->shr_name, new_si->shr_container);
 	}
 
 	return (NERR_Success);
 }
 
-void
-smb_shr_list(int offset, smb_shrlist_t *list)
-{
-	smb_shriter_t iterator;
-	smb_share_t *si;
-	int n = 0;
-
-	bzero(list, sizeof (smb_shrlist_t));
-	smb_shr_iterinit(&iterator);
-
-	while ((si = smb_shr_iterate(&iterator)) != NULL) {
-		if (--offset > 0)
-			continue;
-
-		if ((si->shr_flags & SMB_SHRF_TRANS) &&
-		    ((si->shr_type & STYPE_IPC) == 0)) {
-			bcopy(si, &list->sl_shares[n], sizeof (smb_share_t));
-			if (++n == LMSHARES_PER_REQUEST)
-				break;
-		}
-	}
-
-	list->sl_cnt = n;
-}
-
-
 /*
  * smb_shr_exists
  *
@@ -490,21 +525,89 @@
 boolean_t
 smb_shr_exists(char *sharename)
 {
-	boolean_t exists;
+	boolean_t exists = B_FALSE;
 
 	if (sharename == NULL || *sharename == '\0')
 		return (B_FALSE);
 
-	(void) utf8_strlwr(sharename);
-
-	(void) rw_rdlock(&smb_shr_lock);
-	exists = (ht_find_item(smb_shr_handle, sharename) != NULL);
-	(void) rw_unlock(&smb_shr_lock);
+	if (smb_shr_cache_lock(SMB_SHR_CACHE_RDLOCK) == NERR_Success) {
+		exists = (smb_shr_cache_findent(sharename) != NULL);
+		smb_shr_cache_unlock();
+	}
 
 	return (exists);
 }
 
 /*
+ * If the shared directory does not begin with a /, one will be
+ * inserted as a prefix. If ipaddr is not zero, then also return
+ * information about access based on the host level access lists, if
+ * present. Also return access check if there is an IP address and
+ * shr_accflags.
+ *
+ * The value of smb_chk_hostaccess is checked for an access match.
+ * -1 is wildcard match
+ * 0 is no match
+ * 1 is match
+ *
+ * Precedence is none is checked first followed by ro then rw if
+ * needed.  If x is wildcard (< 0) then check to see if the other
+ * values are a match. If a match, that wins.
+ */
+void
+smb_shr_hostaccess(smb_share_t *si, ipaddr_t ipaddr)
+{
+	int acc = SMB_SHRF_ACC_OPEN;
+
+	/*
+	 * Check to see if there area any share level access
+	 * restrictions.
+	 */
+	if (ipaddr != 0 && (si->shr_flags & SMB_SHRF_ACC_ALL) != 0) {
+		int none = SMB_SHRF_ACC_OPEN;
+		int rw = SMB_SHRF_ACC_OPEN;
+		int ro = SMB_SHRF_ACC_OPEN;
+
+		if (si->shr_flags & SMB_SHRF_ACC_NONE)
+			none = smb_chk_hostaccess(ipaddr, si->shr_access_none);
+		if (si->shr_flags & SMB_SHRF_ACC_RW)
+			rw = smb_chk_hostaccess(ipaddr, si->shr_access_rw);
+		if (si->shr_flags & SMB_SHRF_ACC_RO)
+			ro = smb_chk_hostaccess(ipaddr, si->shr_access_ro);
+
+		/* make first pass to get basic value */
+		if (none != 0)
+			acc = SMB_SHRF_ACC_NONE;
+		else if (ro != 0)
+			acc = SMB_SHRF_ACC_RO;
+		else if (rw != 0)
+			acc = SMB_SHRF_ACC_RW;
+
+		/* make second pass to handle '*' case */
+		if (none < 0) {
+			acc = SMB_SHRF_ACC_NONE;
+			if (ro > 0)
+				acc = SMB_SHRF_ACC_RO;
+			else if (rw > 0)
+				acc = SMB_SHRF_ACC_RW;
+		} else if (ro < 0) {
+			acc = SMB_SHRF_ACC_RO;
+			if (none > 0)
+				acc = SMB_SHRF_ACC_NONE;
+			else if (rw > 0)
+				acc = SMB_SHRF_ACC_RW;
+		} else if (rw < 0) {
+			acc = SMB_SHRF_ACC_RW;
+			if (none > 0)
+				acc = SMB_SHRF_ACC_NONE;
+			else if (ro > 0)
+				acc = SMB_SHRF_ACC_RO;
+		}
+	}
+	si->shr_access_value = acc;	/* return access here */
+}
+
+/*
  * smb_shr_is_special
  *
  * Special share reserved for interprocess communication (IPC$) or
@@ -585,13 +688,10 @@
 /*
  * smb_shr_chkname
  *
- * Check if any invalid char is present in share name. According to
- * MSDN article #236388: "Err Msg: The Share Name Contains Invalid
- * Characters", the list of invalid character is:
+ * Check for invalid characters in a share name.  The list of invalid
+ * characters includes control characters and the following:
  *
  * " / \ [ ] : | < > + ; , ? * =
- *
- * Also rejects if control characters are embedded.
  */
 boolean_t
 smb_shr_chkname(char *sharename)
@@ -616,336 +716,101 @@
 /*
  * smb_shr_get_realpath
  *
- * Derive the real path of a share from the path provided by a
- * Windows client application during the share addition.
+ * Derive the real path for a share from the path provided by a client.
+ * For instance, the real path of C:\ may be /cvol or the real path of
+ * F:\home may be /vol1/home.
  *
- * For instance, the real path of C:\ is /cvol and the
- * real path of F:\home is /vol1/home.
- *
- * clipath  - path provided by the Windows client is in the
+ * clntpath - path provided by the Windows client is in the
  *            format of <drive letter>:\<dir>
  * realpath - path that will be stored as the directory field of
  *            the smb_share_t structure of the share.
- * maxlen   - maximum length fo the realpath buffer
+ * maxlen   - maximum length of the realpath buffer
  *
  * Return LAN Manager network error code.
  */
-/*ARGSUSED*/
 uint32_t
-smb_shr_get_realpath(const char *clipath, char *realpath, int maxlen)
+smb_shr_get_realpath(const char *clntpath, char *realpath, int maxlen)
 {
-	/* XXX do this translation */
-	return (NERR_Success);
-}
-
-/*
- * ============================================
- * Cache management functions
- * ============================================
- */
+	const char *p;
+	int len;
 
-/*
- * smb_shr_cache_create
- *
- * Create the share hash table.
- */
-static uint32_t
-smb_shr_cache_create(void)
-{
-	if (smb_shr_handle == NULL) {
-		(void) rwlock_init(&smb_shr_lock, USYNC_THREAD, 0);
-		(void) rw_wrlock(&smb_shr_lock);
+	if ((p = strchr(clntpath, ':')) != NULL)
+		++p;
+	else
+		p = clntpath;
 
-		smb_shr_handle = ht_create_table(SMB_SHARE_HTAB_SZ,
-		    MAXNAMELEN, 0);
-		if (smb_shr_handle == NULL) {
-			(void) rw_unlock(&smb_shr_lock);
-			return (NERR_InternalError);
-		}
+	(void) strlcpy(realpath, p, maxlen);
+	(void) strcanon(realpath, "/\\");
+	(void) strsubst(realpath, '\\', '/');
 
-		(void) ht_register_callback(smb_shr_handle,
-		    smb_shr_cache_freent);
-		(void) rw_unlock(&smb_shr_lock);
-	}
+	len = strlen(realpath);
+	if ((len > 1) && (realpath[len - 1] == '/'))
+		realpath[len - 1] = '\0';
 
 	return (NERR_Success);
 }
 
-/*
- * smb_shr_cache_destroy
- *
- * Destroys the share hash table.
- */
-static void
-smb_shr_cache_destroy(void)
+void
+smb_shr_list(int offset, smb_shrlist_t *list)
 {
-	if (smb_shr_handle) {
-		(void) rw_wrlock(&smb_shr_lock);
-		ht_destroy_table(smb_shr_handle);
-		(void) rw_unlock(&smb_shr_lock);
-		(void) rwlock_destroy(&smb_shr_lock);
-		smb_shr_handle = NULL;
+	smb_shriter_t iterator;
+	smb_share_t *si;
+	int n = 0;
+
+	bzero(list, sizeof (smb_shrlist_t));
+	smb_shr_iterinit(&iterator);
+
+	while ((si = smb_shr_iterate(&iterator)) != NULL) {
+		if (--offset > 0)
+			continue;
+
+		if ((si->shr_flags & SMB_SHRF_TRANS) &&
+		    ((si->shr_type & STYPE_IPC) == 0)) {
+			bcopy(si, &list->sl_shares[n], sizeof (smb_share_t));
+			if (++n == LMSHARES_PER_REQUEST)
+				break;
+		}
 	}
+
+	list->sl_cnt = n;
 }
 
 /*
- * smb_shr_cache_populate
- *
- * Load shares from sharemgr
+ * ============================================
+ * Private helper/utility functions
+ * ============================================
  */
-/*ARGSUSED*/
-static void *
-smb_shr_cache_populate(void *args)
-{
-	sa_handle_t handle;
-	sa_group_t group, subgroup;
-	char *gstate;
-	boolean_t gdisabled;
-
-	if (smb_shr_cache_create() != NERR_Success) {
-		syslog(LOG_ERR, "share: failed creating the cache");
-		return (NULL);
-	}
-
-	if (smb_shr_create_ipc() != NERR_Success) {
-		syslog(LOG_ERR, "share: failed creating IPC$");
-		return (NULL);
-	}
-
-	if ((handle = sa_init(SA_INIT_SHARE_API)) == NULL) {
-		syslog(LOG_ERR, "share: failed connecting to backend");
-		return (NULL);
-	}
-
-	for (group = sa_get_group(handle, NULL);
-	    group != NULL; group = sa_get_next_group(group)) {
-		gstate = sa_get_group_attr(group, "state");
-		if (gstate == NULL)
-			continue;
-
-		gdisabled = (strcasecmp(gstate, "disabled") == 0);
-		sa_free_attr_string(gstate);
-		if (gdisabled)
-			continue;
-
-		smb_shr_cache_loadgrp(group);
-		for (subgroup = sa_get_sub_group(group);
-		    subgroup != NULL;
-		    subgroup = sa_get_next_group(subgroup)) {
-			smb_shr_cache_loadgrp(subgroup);
-		}
-
-	}
-
-	sa_fini(handle);
-	return (NULL);
-}
-
-static uint32_t
-smb_shr_cache_addent(smb_share_t *si)
-{
-	smb_share_t *cache_ent;
-	uint32_t status = NERR_Success;
-
-	/*
-	 * allocate memory for the entry that needs to be cached.
-	 */
-	if ((cache_ent = malloc(sizeof (smb_share_t))) == NULL)
-		return (ERROR_NOT_ENOUGH_MEMORY);
-
-	bcopy(si, cache_ent, sizeof (smb_share_t));
-
-	(void) utf8_strlwr(cache_ent->shr_name);
-	smb_shr_set_oemname(cache_ent);
-	if ((si->shr_type & STYPE_IPC) == 0)
-		cache_ent->shr_type = STYPE_DISKTREE;
-	cache_ent->shr_type |= smb_shr_is_special(cache_ent->shr_name);
 
-	(void) rw_wrlock(&smb_shr_lock);
-	if (ht_add_item(smb_shr_handle, cache_ent->shr_name, cache_ent)
-	    == NULL) {
-		syslog(LOG_DEBUG, "share: failed adding %s to cache",
-		    cache_ent->shr_name);
-		free(cache_ent);
-		status = NERR_InternalError;
-	}
-	(void) rw_unlock(&smb_shr_lock);
-
-	return (status);
-}
-
-static void
-smb_shr_cache_delent(char *sharename)
-{
-	(void) utf8_strlwr(sharename);
-	(void) rw_wrlock(&smb_shr_lock);
-	(void) ht_remove_item(smb_shr_handle, sharename);
-	(void) rw_unlock(&smb_shr_lock);
-}
-
+/*
+ * Add IPC$ to the cache upon startup.
+ */
 static uint32_t
-smb_shr_cache_chgent(smb_share_t *si)
-{
-	smb_share_t *cache_ent;
-	uint32_t status = NERR_Success;
-
-	/*
-	 * allocate memory for the entry that needs to be cached.
-	 */
-	if ((cache_ent = malloc(sizeof (smb_share_t))) == NULL)
-		return (ERROR_NOT_ENOUGH_MEMORY);
-
-	bcopy(si, cache_ent, sizeof (smb_share_t));
-	(void) utf8_strlwr(cache_ent->shr_name);
-
-	(void) rw_wrlock(&smb_shr_lock);
-	if (ht_replace_item(smb_shr_handle, cache_ent->shr_name, cache_ent)
-	    == NULL) {
-		syslog(LOG_DEBUG, "share: failed modifying %s",
-		    cache_ent->shr_name);
-		free(cache_ent);
-		status = NERR_InternalError;
-	}
-	(void) rw_unlock(&smb_shr_lock);
-
-	return (status);
-}
-
-static uint32_t
-smb_shr_create_autohome(smb_share_t *si)
-{
-	uint32_t status = NERR_Success;
-	int rc;
-
-	if (si->shr_refcnt == 0) {
-		if ((status = smb_shr_cache_addent(si)) != NERR_Success)
-			return (status);
-
-		rc = mlsvc_set_share(SMB_SHROP_ADD, si->shr_path, si->shr_name);
-
-		if (rc != 0) {
-			smb_shr_cache_delent(si->shr_name);
-			return ((rc == ENOENT)
-			    ? NERR_UnknownDevDir : NERR_InternalError);
-		}
-
-		smb_shr_publish(si->shr_name, si->shr_container,
-		    SMB_SHR_PUBLISH);
-	}
-
-	si->shr_refcnt++;
-	smb_shr_set_ahcnt(si->shr_name, si->shr_refcnt);
-	return (status);
-}
-
-static uint32_t
-smb_shr_create_ipc(void)
+smb_shr_addipc(void)
 {
 	smb_share_t ipc;
+	uint32_t status = NERR_InternalError;
 
 	bzero(&ipc, sizeof (smb_share_t));
 	(void) strcpy(ipc.shr_name, "IPC$");
 	(void) strcpy(ipc.shr_cmnt, "Remote IPC");
 	ipc.shr_flags = SMB_SHRF_TRANS;
 	ipc.shr_type = STYPE_IPC;
-	return (smb_shr_cache_addent(&ipc));
-}
 
-/*
- * loads the given resource
- */
-static uint32_t
-smb_shr_cache_loadent(sa_share_t share, sa_resource_t resource)
-{
-	smb_share_t si;
-	uint32_t status;
-
-	if ((status = smb_shr_sa_getent(share, resource, &si)) != NERR_Success)
-		return (status);
-
-	if ((status = smb_shr_cache_addent(&si)) == NERR_Success)
-		smb_shr_publish(si.shr_name, si.shr_container, SMB_SHR_PUBLISH);
-
-	if (status != NERR_Success) {
-		syslog(LOG_ERR, "share: failed loading %s (%d)", si.shr_name,
-		    status);
+	if (smb_shr_cache_lock(SMB_SHR_CACHE_WRLOCK) == NERR_Success) {
+		status = smb_shr_cache_addent(&ipc);
+		smb_shr_cache_unlock();
 	}
 
 	return (status);
 }
 
 /*
- * smb_shr_cache_loadgrp
- *
- * Helper function for smb_shr_cache_populate.
- * It attempts to load the shares contained in the given group.
- * It will check to see if "smb" protocol is enabled or
- * not on the given group. This is needed in the ZFS case where
- * the top level ZFS group won't have "smb" protocol
- * enabled but the sub-groups will.
- */
-static void
-smb_shr_cache_loadgrp(sa_group_t group)
-{
-	sa_share_t share;
-	sa_resource_t resource;
-
-	/* Don't bother if "smb" isn't set on the group */
-	if (sa_get_optionset(group, SMB_PROTOCOL_NAME) == NULL)
-		return;
-
-	for (share = sa_get_share(group, NULL);
-	    share != NULL; share = sa_get_next_share(share)) {
-		for (resource = sa_get_share_resource(share, NULL);
-		    resource != NULL;
-		    resource = sa_get_next_resource(resource)) {
-			(void) smb_shr_cache_loadent(share, resource);
-		}
-	}
-}
-
-/*
- * smb_shr_cache_freent
- *
- * Call back to free given cache entry
- */
-static void
-smb_shr_cache_freent(HT_ITEM *item)
-{
-	if (item && item->hi_data)
-		free(item->hi_data);
-}
-
-/*
- * smb_shr_set_ahcnt
- *
- * sets the autohome reference count for the given share
- */
-static void
-smb_shr_set_ahcnt(char *sharename, int refcnt)
-{
-	smb_share_t *si;
-	HT_ITEM *item;
-
-	(void) rw_wrlock(&smb_shr_lock);
-	item = ht_find_item(smb_shr_handle, sharename);
-	if (item == NULL || item->hi_data == NULL) {
-		(void) rw_unlock(&smb_shr_lock);
-		return;
-	}
-
-	si = (smb_share_t *)item->hi_data;
-	si->shr_refcnt = refcnt;
-	(void) rw_unlock(&smb_shr_lock);
-}
-
-/*
  * smb_shr_set_oemname
  *
- * Generates the OEM name of the given share. If it's
- * shorter than 13 chars it'll be saved in si->shr_oemname.
- * Otherwise si->shr_oemname will be empty and SMB_SHRF_LONGNAME
- * will be set in si->shr_flags.
+ * Generate the OEM name for the specified share.  If the name is
+ * shorter than 13 bytes the oemname will be saved in si->shr_oemname.
+ * Otherwise si->shr_oemname will be empty and SMB_SHRF_LONGNAME will
+ * be set in si->shr_flags.
  */
 static void
 smb_shr_set_oemname(smb_share_t *si)
@@ -986,77 +851,357 @@
 
 /*
  * ============================================
- * Interfaces to sharemgr
+ * Cache management functions
+ *
+ * All cache functions are private
  * ============================================
  */
 
 /*
- * Stores the given share in sharemgr
+ * Create the share cache (hash table).
  */
 static uint32_t
-smb_shr_sa_addent(smb_share_t *si)
+smb_shr_cache_create(void)
 {
-	sa_handle_t handle;
-	sa_share_t share;
-	sa_group_t group;
-	sa_resource_t resource;
-	boolean_t share_created = B_FALSE;
-	int err;
+	uint32_t status = NERR_Success;
 
-	if ((handle = sa_init(SA_INIT_SHARE_API)) == NULL)
-		return (NERR_InternalError);
-
-	share = sa_find_share(handle, si->shr_path);
-	if (share == NULL) {
-		group = smb_shr_sa_getdefgrp(handle);
-		if (group == NULL) {
-			sa_fini(handle);
-			return (NERR_InternalError);
+	(void) mutex_lock(&smb_shr_cache.sc_mtx);
+	switch (smb_shr_cache.sc_state) {
+	case SMB_SHR_CACHE_STATE_NONE:
+		smb_shr_cache.sc_cache = ht_create_table(SMB_SHR_HTAB_SZ,
+		    MAXNAMELEN, 0);
+		if (smb_shr_cache.sc_cache == NULL) {
+			status = NERR_InternalError;
+			break;
 		}
 
-		share = sa_add_share(group, si->shr_path, SA_SHARE_PERMANENT,
-		    &err);
-		if (share == NULL) {
-			sa_fini(handle);
-			return (NERR_InternalError);
-		}
-		share_created = B_TRUE;
+		(void) ht_register_callback(smb_shr_cache.sc_cache,
+		    smb_shr_cache_freent);
+		smb_shr_cache.sc_nops = 0;
+		smb_shr_cache.sc_state = SMB_SHR_CACHE_STATE_CREATED;
+		break;
+
+	default:
+		assert(0);
+		status = NERR_InternalError;
+		break;
+	}
+	(void) mutex_unlock(&smb_shr_cache.sc_mtx);
+
+	return (status);
+}
+
+/*
+ * Destroy the share cache (hash table).
+ * Wait for inflight/pending operations to finish or abort before
+ * destroying the cache.
+ */
+static void
+smb_shr_cache_destroy(void)
+{
+	(void) mutex_lock(&smb_shr_cache.sc_mtx);
+	if (smb_shr_cache.sc_state == SMB_SHR_CACHE_STATE_CREATED) {
+		smb_shr_cache.sc_state = SMB_SHR_CACHE_STATE_DESTROYING;
+		while (smb_shr_cache.sc_nops > 0)
+			(void) cond_wait(&smb_shr_cache.sc_cv,
+			    &smb_shr_cache.sc_mtx);
+
+		smb_shr_cache.sc_cache = NULL;
+		smb_shr_cache.sc_state = SMB_SHR_CACHE_STATE_NONE;
+	}
+	(void) mutex_unlock(&smb_shr_cache.sc_mtx);
+}
+
+/*
+ * If the cache is in "created" state, lock the cache for read
+ * or read/write based on the specified mode.
+ *
+ * Whenever a lock is granted, the number of inflight cache
+ * operations is incremented.
+ */
+static uint32_t
+smb_shr_cache_lock(int mode)
+{
+	(void) mutex_lock(&smb_shr_cache.sc_mtx);
+	switch (smb_shr_cache.sc_state) {
+	case SMB_SHR_CACHE_STATE_CREATED:
+		smb_shr_cache.sc_nops++;
+		break;
+
+	case SMB_SHR_CACHE_STATE_DESTROYING:
+		(void) mutex_unlock(&smb_shr_cache.sc_mtx);
+		return (NERR_InternalError);
+
+	case SMB_SHR_CACHE_STATE_NONE:
+	default:
+		assert(0);
+		(void) mutex_unlock(&smb_shr_cache.sc_mtx);
+		return (NERR_InternalError);
+
+	}
+	(void) mutex_unlock(&smb_shr_cache.sc_mtx);
+
+	/*
+	 * Lock has to be taken outside the mutex otherwise
+	 * there could be a deadlock
+	 */
+	if (mode == SMB_SHR_CACHE_RDLOCK)
+		(void) rw_rdlock(&smb_shr_cache.sc_cache_lck);
+	else
+		(void) rw_wrlock(&smb_shr_cache.sc_cache_lck);
+
+	return (NERR_Success);
+}
+
+/*
+ * Decrement the number of inflight operations and then unlock.
+ */
+static void
+smb_shr_cache_unlock(void)
+{
+	(void) mutex_lock(&smb_shr_cache.sc_mtx);
+	assert(smb_shr_cache.sc_nops > 0);
+	smb_shr_cache.sc_nops--;
+	(void) cond_broadcast(&smb_shr_cache.sc_cv);
+	(void) mutex_unlock(&smb_shr_cache.sc_mtx);
+
+	(void) rw_unlock(&smb_shr_cache.sc_cache_lck);
+}
+
+/*
+ * Return the total number of shares
+ */
+static int
+smb_shr_cache_count(void)
+{
+	return (ht_get_total_items(smb_shr_cache.sc_cache));
+}
+
+/*
+ * looks up the given share name in the cache and if it
+ * finds a match returns a pointer to the cached entry.
+ * Note that since a pointer is returned this function
+ * MUST be protected by smb_shr_cache_lock/unlock pair
+ */
+static smb_share_t *
+smb_shr_cache_findent(char *sharename)
+{
+	HT_ITEM *item;
+
+	(void) utf8_strlwr(sharename);
+	item = ht_find_item(smb_shr_cache.sc_cache, sharename);
+	if (item && item->hi_data)
+		return ((smb_share_t *)item->hi_data);
+
+	return (NULL);
+}
+
+/*
+ * Return a pointer to the first/next entry in
+ * the cache based on the given iterator.
+ *
+ * Calls to this function MUST be protected by
+ * smb_shr_cache_lock/unlock.
+ */
+static smb_share_t *
+smb_shr_cache_iterate(smb_shriter_t *shi)
+{
+	HT_ITEM *item;
+
+	if (shi->si_first) {
+		item = ht_findfirst(smb_shr_cache.sc_cache, &shi->si_hashiter);
+		shi->si_first = B_FALSE;
+	} else {
+		item = ht_findnext(&shi->si_hashiter);
 	}
 
-	resource = sa_get_share_resource(share, si->shr_name);
-	if (resource == NULL) {
-		resource = sa_add_resource(share, si->shr_name,
-		    SA_SHARE_PERMANENT, &err);
-		if (resource == NULL)
-			goto failure;
+	if (item && item->hi_data)
+		return ((smb_share_t *)item->hi_data);
+
+	return (NULL);
+}
+
+/*
+ * Add the specified share to the cache.  Memory needs to be allocated
+ * for the cache entry and the passed information is copied to the
+ * allocated space.
+ */
+static uint32_t
+smb_shr_cache_addent(smb_share_t *si)
+{
+	smb_share_t *cache_ent;
+	uint32_t status = NERR_Success;
+
+	if ((cache_ent = malloc(sizeof (smb_share_t))) == NULL)
+		return (ERROR_NOT_ENOUGH_MEMORY);
+
+	bcopy(si, cache_ent, sizeof (smb_share_t));
+
+	(void) utf8_strlwr(cache_ent->shr_name);
+	smb_shr_set_oemname(cache_ent);
+
+	if ((si->shr_type & STYPE_IPC) == 0)
+		cache_ent->shr_type = STYPE_DISKTREE;
+	cache_ent->shr_type |= smb_shr_is_special(cache_ent->shr_name);
+
+	if (smb_shr_is_admin(cache_ent->shr_name))
+		cache_ent->shr_flags |= SMB_SHRF_ADMIN;
+
+	if (si->shr_flags & SMB_SHRF_AUTOHOME)
+		cache_ent->shr_refcnt = 1;
+
+	if (ht_add_item(smb_shr_cache.sc_cache, cache_ent->shr_name, cache_ent)
+	    == NULL) {
+		syslog(LOG_DEBUG, "share: %s: cache update failed",
+		    cache_ent->shr_name);
+		free(cache_ent);
+		status = NERR_InternalError;
 	}
 
-	if (sa_set_resource_attr(resource, "description", si->shr_cmnt)
-	    != SA_OK) {
-		goto failure;
+	return (status);
+}
+
+/*
+ * Delete the specified share from the cache.
+ */
+static void
+smb_shr_cache_delent(char *sharename)
+{
+	(void) utf8_strlwr(sharename);
+	(void) ht_remove_item(smb_shr_cache.sc_cache, sharename);
+}
+
+/*
+ * Call back to free the given cache entry.
+ */
+static void
+smb_shr_cache_freent(HT_ITEM *item)
+{
+	if (item && item->hi_data)
+		free(item->hi_data);
+}
+
+/*
+ * ============================================
+ * Interfaces to sharemgr
+ *
+ * All functions in this section are private
+ * ============================================
+ */
+
+/*
+ * Load shares from sharemgr
+ */
+/*ARGSUSED*/
+static void *
+smb_shr_sa_loadall(void *args)
+{
+	sa_handle_t handle;
+	sa_group_t group, subgroup;
+	char *gstate;
+	boolean_t gdisabled;
+
+	if ((handle = sa_init(SA_INIT_SHARE_API)) == NULL) {
+		syslog(LOG_ERR, "share: failed to get libshare API handle");
+		return (NULL);
 	}
 
-	if (sa_set_resource_attr(resource, SMB_SHROPT_AD_CONTAINER,
-	    si->shr_container) != SA_OK) {
-		goto failure;
+	for (group = sa_get_group(handle, NULL);
+	    group != NULL; group = sa_get_next_group(group)) {
+		gstate = sa_get_group_attr(group, "state");
+		if (gstate == NULL)
+			continue;
+
+		gdisabled = (strcasecmp(gstate, "disabled") == 0);
+		sa_free_attr_string(gstate);
+		if (gdisabled)
+			continue;
+
+		smb_shr_sa_loadgrp(group);
+
+		for (subgroup = sa_get_sub_group(group);
+		    subgroup != NULL;
+		    subgroup = sa_get_next_group(subgroup)) {
+			smb_shr_sa_loadgrp(subgroup);
+		}
+
 	}
 
 	sa_fini(handle);
-	return (NERR_Success);
-
-failure:
-	if (share_created && (share != NULL))
-		(void) sa_remove_share(share);
-
-	if (resource != NULL)
-		(void) sa_remove_resource(resource);
-
-	sa_fini(handle);
-	return (NERR_InternalError);
+	return (NULL);
 }
 
+/*
+ * Load the shares contained in the specified group.
+ *
+ * Don't process groups on which the smb protocol is disabled.
+ * The top level ZFS group won't have the smb protocol enabled
+ * but sub-groups will.
+ *
+ * We will tolerate a limited number of errors and then give
+ * up on the current group.  A typical error might be that the
+ * shared directory no longer exists.
+ */
+static void
+smb_shr_sa_loadgrp(sa_group_t group)
+{
+	sa_share_t share;
+	sa_resource_t resource;
+	int error_count = 0;
+
+	if (sa_get_optionset(group, SMB_PROTOCOL_NAME) == NULL)
+		return;
+
+	for (share = sa_get_share(group, NULL);
+	    share != NULL;
+	    share = sa_get_next_share(share)) {
+		for (resource = sa_get_share_resource(share, NULL);
+		    resource != NULL;
+		    resource = sa_get_next_resource(resource)) {
+			if (smb_shr_sa_load(share, resource))
+				++error_count;
+
+			if (error_count > SMB_SHR_ERROR_THRESHOLD)
+				break;
+		}
+
+		if (error_count > SMB_SHR_ERROR_THRESHOLD)
+			break;
+	}
+}
+
+/*
+ * Load a share definition from sharemgr and add it to the cache.
+ */
 static uint32_t
-smb_shr_sa_getent(sa_share_t share, sa_resource_t resource, smb_share_t *si)
+smb_shr_sa_load(sa_share_t share, sa_resource_t resource)
+{
+	smb_share_t si;
+	uint32_t status;
+
+	if ((status = smb_shr_sa_get(share, resource, &si)) != NERR_Success) {
+		syslog(LOG_DEBUG, "share: failed to load %s (%d)",
+		    si.shr_name, status);
+		return (status);
+	}
+
+	if ((status = smb_shr_add(&si)) != NERR_Success) {
+		syslog(LOG_DEBUG, "share: failed to cache %s (%d)",
+		    si.shr_name, status);
+		return (status);
+	}
+
+	return (NERR_Success);
+}
+
+/*
+ * Read the specified share information from sharemgr and return
+ * it in the given smb_share_t structure.
+ *
+ * Shares read from sharemgr are marked as permanent/persistent.
+ */
+static uint32_t
+smb_shr_sa_get(sa_share_t share, sa_resource_t resource, smb_share_t *si)
 {
 	sa_property_t prop;
 	sa_optionset_t opts;
@@ -1073,7 +1218,6 @@
 	}
 
 	bzero(si, sizeof (smb_share_t));
-	/* Share is read from SMF so it should be permanent */
 	si->shr_flags = SMB_SHRF_PERM;
 
 	(void) strlcpy(si->shr_path, path, sizeof (si->shr_path));
@@ -1103,92 +1247,76 @@
 			free(val);
 		}
 	}
-	sa_free_derived_optionset(opts);
 
-	return (NERR_Success);
-}
-
-/*
- * Removes the share from sharemgr
- */
-static uint32_t
-smb_shr_sa_delent(smb_share_t *si)
-{
-	sa_handle_t handle;
-	sa_share_t share;
-	sa_resource_t resource;
-
-	if ((handle = sa_init(SA_INIT_SHARE_API)) == NULL)
-		return (NERR_InternalError);
-
-	if ((share = sa_find_share(handle, si->shr_path)) == NULL) {
-		sa_fini(handle);
-		return (NERR_InternalError);
-	}
-
-	if ((resource = sa_get_share_resource(share, si->shr_name)) == NULL) {
-		sa_fini(handle);
-		return (NERR_InternalError);
-	}
-
-	if (sa_remove_resource(resource) != SA_OK) {
-		sa_fini(handle);
-		return (NERR_InternalError);
+	prop = (sa_property_t)sa_get_property(opts, SHOPT_NONE);
+	if (prop != NULL) {
+		if ((val = sa_get_property_attr(prop, "value")) != NULL) {
+			(void) strlcpy(si->shr_access_none, val,
+			    sizeof (si->shr_access_none));
+			free(val);
+			si->shr_flags |= SMB_SHRF_ACC_NONE;
+		}
 	}
 
-	sa_fini(handle);
-	return (NERR_Success);
-}
-
-/*
- * smb_shr_sa_getdefgrp
- *
- * If default group for CIFS shares (i.e. "smb") exists
- * then it will return the group handle, otherwise it will
- * create the group and return the handle.
- *
- * All the shares created by CIFS clients (this is only possible
- * via RPC) will be added to "smb" groups.
- */
-static sa_group_t
-smb_shr_sa_getdefgrp(sa_handle_t handle)
-{
-	sa_group_t group = NULL;
-	int err;
-
-	group = sa_get_group(handle, SMB_DEFAULT_SHARE_GROUP);
-	if (group != NULL)
-		return (group);
-
-	group = sa_create_group(handle, SMB_DEFAULT_SHARE_GROUP, &err);
-	if (group == NULL)
-		return (NULL);
-
-	if (sa_create_optionset(group, SMB_DEFAULT_SHARE_GROUP) == NULL) {
-		(void) sa_remove_group(group);
-		group = NULL;
+	prop = (sa_property_t)sa_get_property(opts, SHOPT_RO);
+	if (prop != NULL) {
+		if ((val = sa_get_property_attr(prop, "value")) != NULL) {
+			(void) strlcpy(si->shr_access_ro, val,
+			    sizeof (si->shr_access_ro));
+			free(val);
+			si->shr_flags |= SMB_SHRF_ACC_RO;
+		}
 	}
 
-	return (group);
+	prop = (sa_property_t)sa_get_property(opts, SHOPT_RW);
+	if (prop != NULL) {
+		if ((val = sa_get_property_attr(prop, "value")) != NULL) {
+			(void) strlcpy(si->shr_access_rw, val,
+			    sizeof (si->shr_access_rw));
+			free(val);
+			si->shr_flags |= SMB_SHRF_ACC_RW;
+		}
+	}
+
+	sa_free_derived_optionset(opts);
+	return (NERR_Success);
 }
 
 /*
  * ============================================
  * Share publishing functions
+ *
+ * All the functions are private
  * ============================================
  */
 
+static void
+smb_shr_publish(const char *sharename, const char *container)
+{
+	smb_shr_publisher_queue(sharename, container, SMB_SHR_PUBLISH);
+}
+
+static void
+smb_shr_unpublish(const char *sharename, const char *container)
+{
+	smb_shr_publisher_queue(sharename, container, SMB_SHR_UNPUBLISH);
+}
+
 /*
- * Put the share on publish queue.
+ * In domain mode, put a share on the publisher queue.
+ * This is a no-op if the smb service is in Workgroup mode.
  */
 static void
-smb_shr_publish(const char *sharename, const char *container, char op)
+smb_shr_publisher_queue(const char *sharename, const char *container, char op)
 {
 	smb_shr_pitem_t *item = NULL;
 
 	if (container == NULL || *container == '\0')
 		return;
 
+	if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN)
+		return;
+
 	(void) mutex_lock(&ad_queue.spq_mtx);
 	switch (ad_queue.spq_state) {
 	case SMB_SHR_PQS_READY:
@@ -1200,10 +1328,8 @@
 	}
 	(void) mutex_unlock(&ad_queue.spq_mtx);
 
-	if ((item = malloc(sizeof (smb_shr_pitem_t))) == NULL) {
-		syslog(LOG_DEBUG, "failed allocating share publish item");
+	if ((item = malloc(sizeof (smb_shr_pitem_t))) == NULL)
 		return;
-	}
 
 	item->spi_op = op;
 	(void) strlcpy(item->spi_name, sharename, sizeof (item->spi_name));
@@ -1212,17 +1338,24 @@
 
 	(void) mutex_lock(&ad_queue.spq_mtx);
 	list_insert_tail(&ad_queue.spq_list, item);
-	ad_queue.spq_cnt++;
 	(void) cond_signal(&ad_queue.spq_cv);
 	(void) mutex_unlock(&ad_queue.spq_mtx);
 }
 
+/*
+ * Publishing won't be activated if the smb service is running in
+ * Workgroup mode.
+ */
 static int
 smb_shr_publisher_start(void)
 {
+	pthread_t publish_thr;
 	pthread_attr_t tattr;
 	int rc;
 
+	if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN)
+		return (0);
+
 	(void) mutex_lock(&ad_queue.spq_mtx);
 	if (ad_queue.spq_state != SMB_SHR_PQS_NOQUEUE) {
 		(void) mutex_unlock(&ad_queue.spq_mtx);
@@ -1237,8 +1370,7 @@
 
 	(void) pthread_attr_init(&tattr);
 	(void) pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
-	rc = pthread_create(&smb_shr_publish_thr, &tattr,
-	    smb_shr_publisher, 0);
+	rc = pthread_create(&publish_thr, &tattr, smb_shr_publisher, 0);
 	(void) pthread_attr_destroy(&tattr);
 
 	return (rc);
@@ -1247,6 +1379,9 @@
 static void
 smb_shr_publisher_stop(void)
 {
+	if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN)
+		return;
+
 	(void) mutex_lock(&ad_queue.spq_mtx);
 	switch (ad_queue.spq_state) {
 	case SMB_SHR_PQS_READY:
@@ -1261,8 +1396,13 @@
 }
 
 /*
- * This functions waits to be signaled and once running
- * will publish/unpublish any items in the ad_queue
+ * This is the publisher daemon thread.  While running, the thread waits
+ * on a conditional variable until notified that a share needs to be
+ * [un]published or that the thread should be terminated.
+ *
+ * Entries may remain in the outgoing queue if the Active Directory
+ * service is inaccessible, in which case the thread wakes up every 60
+ * seconds to retry.
  */
 /*ARGSUSED*/
 static void *
@@ -1271,93 +1411,111 @@
 	smb_ads_handle_t *ah;
 	smb_shr_pitem_t *shr;
 	list_t publist;
+	timestruc_t pubretry;
 	char hostname[MAXHOSTNAMELEN];
 
 	(void) mutex_lock(&ad_queue.spq_mtx);
-	if (ad_queue.spq_state == SMB_SHR_PQS_READY) {
-		ad_queue.spq_state = SMB_SHR_PQS_PUBLISHING;
-	} else {
+	if (ad_queue.spq_state != SMB_SHR_PQS_READY) {
 		(void) mutex_unlock(&ad_queue.spq_mtx);
 		return (NULL);
 	}
+	ad_queue.spq_state = SMB_SHR_PQS_PUBLISHING;
 	(void) mutex_unlock(&ad_queue.spq_mtx);
 
 	(void) smb_gethostname(hostname, MAXHOSTNAMELEN, 0);
+
 	list_create(&publist, sizeof (smb_shr_pitem_t),
 	    offsetof(smb_shr_pitem_t, spi_lnd));
 
 	for (;;) {
 		(void) mutex_lock(&ad_queue.spq_mtx);
-		while ((ad_queue.spq_cnt == 0) &&
-		    (ad_queue.spq_state == SMB_SHR_PQS_PUBLISHING))
-			(void) cond_wait(&ad_queue.spq_cv, &ad_queue.spq_mtx);
+
+		while (list_is_empty(&ad_queue.spq_list) &&
+		    (ad_queue.spq_state == SMB_SHR_PQS_PUBLISHING)) {
+			if (list_is_empty(&publist)) {
+				(void) cond_wait(&ad_queue.spq_cv,
+				    &ad_queue.spq_mtx);
+			} else {
+				pubretry.tv_sec = 60;
+				pubretry.tv_nsec = 0;
+				(void) cond_reltimedwait(&ad_queue.spq_cv,
+				    &ad_queue.spq_mtx, &pubretry);
+				break;
+			}
+		}
 
 		if (ad_queue.spq_state != SMB_SHR_PQS_PUBLISHING) {
 			(void) mutex_unlock(&ad_queue.spq_mtx);
 			break;
 		}
 
-		if ((ah = smb_ads_open()) == NULL) {
-			(void) mutex_unlock(&ad_queue.spq_mtx);
-			continue;
-		}
-
 		/*
-		 * Transfer queued items to the local list so the mutex
-		 * can be quickly released
+		 * Transfer queued items to the local list so that
+		 * the mutex can be released.
 		 */
 		while ((shr = list_head(&ad_queue.spq_list)) != NULL) {
 			list_remove(&ad_queue.spq_list, shr);
-			ad_queue.spq_cnt--;
 			list_insert_tail(&publist, shr);
 		}
+
 		(void) mutex_unlock(&ad_queue.spq_mtx);
 
-		smb_shr_publisher_send(ah, &publist, hostname);
-		smb_ads_close(ah);
+		if ((ah = smb_ads_open()) != NULL) {
+			smb_shr_publisher_send(ah, &publist, hostname);
+			smb_ads_close(ah);
+		}
 	}
 
-	/* Remove any leftover items from publishing queue */
 	(void) mutex_lock(&ad_queue.spq_mtx);
-	while ((shr = list_head(&ad_queue.spq_list)) != NULL) {
-		list_remove(&ad_queue.spq_list, shr);
-		free(shr);
-	}
-	ad_queue.spq_cnt = 0;
+	smb_shr_publisher_flush(&ad_queue.spq_list);
 	list_destroy(&ad_queue.spq_list);
 	ad_queue.spq_state = SMB_SHR_PQS_NOQUEUE;
 	(void) mutex_unlock(&ad_queue.spq_mtx);
 
+	smb_shr_publisher_flush(&publist);
 	list_destroy(&publist);
 	return (NULL);
 }
 
 /*
- * Takes item from the given list and [un]publish them one by one.
- * In each iteration it checks the status of the publisher thread
- * and if it's been stopped then it continues to just empty the list
+ * Remove items from the specified queue and [un]publish them.
  */
 static void
 smb_shr_publisher_send(smb_ads_handle_t *ah, list_t *publist, const char *host)
 {
 	smb_shr_pitem_t *shr;
-	boolean_t publish = B_TRUE;
 
 	while ((shr = list_head(publist)) != NULL) {
-		list_remove(publist, shr);
-		if (publish) {
+		(void) mutex_lock(&ad_queue.spq_mtx);
+		if (ad_queue.spq_state != SMB_SHR_PQS_PUBLISHING) {
 			(void) mutex_unlock(&ad_queue.spq_mtx);
-			if (ad_queue.spq_state != SMB_SHR_PQS_PUBLISHING)
-				publish = B_FALSE;
-			(void) mutex_unlock(&ad_queue.spq_mtx);
+			return;
+		}
+		(void) mutex_unlock(&ad_queue.spq_mtx);
 
-			if (shr->spi_op == SMB_SHR_PUBLISH)
-				(void) smb_ads_publish_share(ah, shr->spi_name,
-				    NULL, shr->spi_container, host);
-			else
-				(void) smb_ads_remove_share(ah, shr->spi_name,
-				    NULL, shr->spi_container, host);
-		}
+		list_remove(publist, shr);
+
+		if (shr->spi_op == SMB_SHR_PUBLISH)
+			(void) smb_ads_publish_share(ah, shr->spi_name,
+			    NULL, shr->spi_container, host);
+		else
+			(void) smb_ads_remove_share(ah, shr->spi_name,
+			    NULL, shr->spi_container, host);
+
 		free(shr);
 	}
 }
+
+/*
+ * Flush all remaining items from the specified list/queue.
+ */
+static void
+smb_shr_publisher_flush(list_t *lst)
+{
+	smb_shr_pitem_t *shr;
+
+	while ((shr = list_head(lst)) != NULL) {
+		list_remove(lst, shr);
+		free(shr);
+	}
+}