From ab6cd5cfc44ae3b44097d8d02c5a041f0ab51999 Mon Sep 17 00:00:00 2001 From: Turbo Fredriksson Date: Sat, 21 Dec 2013 07:34:08 +0000 Subject: [PATCH] => #1476: turbo/smbfs_registry-shares - Rewrite of the Libshare/SMBFS sharing property (sharesmb). Rewrite of the Libshare/SMBFS * Add support for options to 'sharesmb'. Fixes: #1182 + Move nfs.c:foreach_nfs_shareopt() to libshare.c:foreach_shareopt() so that it can be (re)used in smb.c (and later iscsi.c). + Call net(8) with the guest_ok option. + Rewrite the sharesmb part of the zfs(8) manpage. + Add more examples + Inform about the (new) options to sharesmb. + Add STDERR_VERBOSE to libzfs_run_process() so that we can catch any errors from net(8). + Don't call smb_retrieve_shares() after enabling a share. No point, it's done when really needed anyway. + Support 'y', 'yes' and 'true' for guest_ok. * If sharesmb=on, just exit cleanly directly at the top of get_smb_shareopts_cb(). * Remove param 2 and 3 from smb_enable_share_one(). That is availible in the first param - the impl_share struct. * Extra debugging added in smb_enable_share_one() - print all our options just before we call net(8). * Add support for optional, extra script /sbin/zfs_share_smbfs to run after shareing is done. + Add an example zfs_share_smbfs script * The option sharesmb should not be inherited by childs. Samba is perfectly able to show child file systems... * Use dataset name in comment, not the smb share name. * Use REGISTRY SHARES instead of USER SHARES + These are a lot more configurable (more possibility to customize the share). + Remove the 'acl' option and replace it with 'writeable' instead. + On some machines, there is a "global" share. This isn't really a share, but the possibility to modify global options. So first get a line of _real_ shares using 'net conf listshares' and _then_ cyckle through them retrieving info with 'net conf showshare '. + Add an example on how to modify a share after ZFS share and what man pages to use for more info. * Speedups (less share retreivals) and make sure the 'net' command is executable. + Since smb_shares is global, we only retreive shares if it's NULL in smb_retrieve_shares(). It should limit the number of times we fork() and read shares (which takes time). + If the net command don't exist (is executable), don't even register the smb fstype in libshare_smb_init(). + access() fix - check if NET_CMD_PATH is _executable_ not just 'exists'. * Run cstyle.pl to fix any code style problems. * Output error if 'net' can't talk to samba. * Improve error message if 'net' isn't executable. * Move the checks from smb_available() to libshare_smb_init() to avoid doing them multiple times. * Convert homemade linked list to the ZFS versions list_* functions (which uses a link_t). * Script to list SMB shares added. * Comment out a call to zfs_unshare_smb(). Don't seem to be needed. This because zfs_unshare_smb() is called twice if setting 'sharesmb=off' - once 'somewhere else' and once here (this one AFTER the share have been unshared). That makes this one fail! Don't know if this is correct, but it works to comment out this... --- lib/libshare/list_smbfs.sh | 12 + lib/libshare/smb.c | 635 +++++++++++++++++++++++---------- lib/libshare/smb.h | 20 +- lib/libshare/zfs_share_smbfs | 16 + lib/libzfs/libzfs_changelist.c | 15 +- man/man8/zfs.8 | 113 +++++- module/zcommon/zfs_prop.c | 4 +- scripts/smb.sh | 256 ++++++------- 8 files changed, 709 insertions(+), 362 deletions(-) create mode 100755 lib/libshare/list_smbfs.sh create mode 100644 lib/libshare/zfs_share_smbfs diff --git a/lib/libshare/list_smbfs.sh b/lib/libshare/list_smbfs.sh new file mode 100755 index 000000000000..69e81708e412 --- /dev/null +++ b/lib/libshare/list_smbfs.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +i=1 +net conf listshares | sort --ignore-case | \ +while read share; do + path=`net conf showshare $share | grep path | sed 's@.* = @@'` + guestok=`net conf showshare $share | grep 'guest ok' | sed 's@.* = @@'` + readonly=`net conf showshare $share | grep 'read only' | sed 's@.* = @@'` + + printf "%6s: %-80s %-73s guestok=%-3s ro=%-3s\n" $i $share $path $guestok $readonly + i=`expr $i + 1` +done diff --git a/lib/libshare/smb.c b/lib/libshare/smb.c index 1ac1a8d27f56..b968cbc0c7c1 100644 --- a/lib/libshare/smb.c +++ b/lib/libshare/smb.c @@ -28,21 +28,21 @@ * shares using the 'net share' command that comes with Samba. * * TESTING - * Make sure that samba listens to 'localhost' (127.0.0.1) and that the options - * 'usershare max shares' and 'usershare owner only' have been rewied/set - * accordingly (see zfs(8) for information). + * Make sure that samba listens to 'localhost' (127.0.0.1) and + * that the option 'registry shares' is set to 'yes'. * * Once configuration in samba have been done, test that this * works with the following three commands (in this case, my ZFS * filesystem is called 'share/Test1'): * - * (root)# net -U root -S 127.0.0.1 usershare add Test1 /share/Test1 \ - * "Comment: /share/Test1" "Everyone:F" - * (root)# net usershare list | grep -i test - * (root)# net -U root -S 127.0.0.1 usershare delete Test1 + * (root)# net -U root -S 127.0.0.1 conf addshare Test1 /share/Test1 \ + * writeable=y guest_ok=y "Dataset name: share/Test1" + * (root)# net conf list | grep -i '^\[test' + * (root)# net -U root -S 127.0.0.1 conf delshare Test1 * - * The first command will create a user share that gives everyone full access. - * To limit the access below that, use normal UNIX commands (chmod, chown etc). + * The first command will create a registry share that gives + * everyone full access. To limit the access below that, use + * normal UNIX commands (chmod, chown etc). */ #include @@ -55,141 +55,335 @@ #include #include #include +#include #include #include #include "libshare_impl.h" #include "smb.h" +#if !defined(offsetof) +#define offsetof(s, m) ((size_t)(&(((s *)0)->m))) +#endif + static boolean_t smb_available(void); +static int smb_validate_shareopts(const char *shareopts); static sa_fstype_t *smb_fstype; +static list_t all_smb_shares_list; + +typedef struct smb_shares_list_s { + char name[SMB_NAME_MAX]; + list_node_t next; +} smb_shares_list_t; + +/* http://stackoverflow.com/questions/122616/how-do-i-trim-leading-trailing-whitespace-in-a-standard-way */ +void trim_whitespace(char *s) { + char *p = s; + int l = strlen(p); + + while (isspace(p[l - 1])) p[--l] = 0; + while (*p && isspace(*p)) ++p, --l; + + memmove(s, p, l + 1); +} + +static smb_share_t * +smb_retrieve_share_info(char *share_name) +{ + int ret, buffer_len; + FILE *sharesmb_temp_fp; + char buffer[512], cmd[PATH_MAX]; + char *token, *key, *value; + char *dup_value, *path = NULL, *comment = NULL, *guest_ok = NULL, + *read_only = NULL; + smb_share_t *share = NULL; + + /* CMD: net conf showshare */ + ret = snprintf(cmd, sizeof (cmd), "%s -S %s conf showshare %s", + NET_CMD_PATH, NET_CMD_ARG_HOST, share_name); + if (ret < 0 || ret >= sizeof (cmd)) + return (NULL); + + sharesmb_temp_fp = popen(cmd, "r"); + if (sharesmb_temp_fp == NULL) + return (NULL); + + while (fgets(buffer, sizeof (buffer), sharesmb_temp_fp) != 0) { + /* Trim trailing new-line character(s). */ + buffer_len = strlen(buffer); + while (buffer_len > 0) { + buffer_len--; + if (buffer[buffer_len] == '\r' || + buffer[buffer_len] == '\n') { + buffer[buffer_len] = 0; + } else + break; + } + + /* Split the line in two, separated by '=' */ + token = strchr(buffer, '='); + if (token == NULL) + /* This will also catch empty lines */ + continue; + + key = buffer; + value = token + 1; + *token = '\0'; + + trim_whitespace(key); + trim_whitespace(value); + + dup_value = strdup(value); + if (dup_value == NULL) { + if (pclose(sharesmb_temp_fp) != 0) + fprintf(stderr, "Failed to pclose stream\n"); + return (NULL); + } + + if (strcmp(key, "path") == 0) + path = dup_value; + if (strcmp(key, "comment") == 0) + comment = dup_value; + if (strcmp(key, "guest ok") == 0) + guest_ok = dup_value; + if (strcmp(key, "read only") == 0) + read_only = dup_value; + + if (path == NULL || comment == NULL || + guest_ok == NULL || read_only == NULL) + continue; /* Incomplete share definition */ + else { + share = (smb_share_t *) malloc(sizeof (smb_share_t)); + if (share == NULL) { + if (pclose(sharesmb_temp_fp) != 0) + fprintf(stderr, + "Failed to pclose stream\n"); + return (NULL); + } + + strncpy(share->name, share_name, sizeof (share->name)); + share->name[sizeof (share->name)-1] = '\0'; + + strncpy(share->path, path, sizeof (share->path)); + share->path[sizeof (share->path)-1] = '\0'; + + strncpy(share->comment, comment, + sizeof (share->comment)); + share->comment[sizeof (share->comment)-1] = '\0'; + + if (strcmp(guest_ok, "yes") == 0) + share->guest_ok = B_TRUE; + else + share->guest_ok = B_FALSE; + + if (strcmp(read_only, "no") == 0) + share->writeable = B_TRUE; + else + share->writeable = B_FALSE; + + path = NULL; + comment = NULL; + guest_ok = NULL; + read_only = NULL; + } + } + + if (pclose(sharesmb_temp_fp) != 0) + fprintf(stderr, "Failed to pclose stream\n"); + + return (share); +} /* * Retrieve the list of SMB shares. + * Do this only if we haven't already. + * TODO: That doesn't work exactly as intended. Threading? */ static int smb_retrieve_shares(void) { - int rc = SA_OK; - char file_path[PATH_MAX], line[512], *token, *key, *value; - char *dup_value, *path = NULL, *comment = NULL, *name = NULL; - char *guest_ok = NULL; - DIR *shares_dir; - FILE *share_file_fp = NULL; - struct dirent *directory; - struct stat eStat; - smb_share_t *shares, *new_shares = NULL; - - /* opendir(), stat() */ - shares_dir = opendir(SHARE_DIR); - if (shares_dir == NULL) + int ret, buffer_len; + FILE *sharesmb_temp_fp; + char buffer[512], cmd[PATH_MAX]; + smb_share_t *share_info = NULL; + +// if (!list_is_empty(&all_smb_shares_list)) { +//fprintf(stderr, "smb_retrieve_shares: !list_is_empty()\n"); +// /* Try to limit the number of times we do this */ +// return (SA_OK); +// } + + /* Create the global share list */ + list_create(&all_smb_shares_list, sizeof (smb_share_t), + offsetof(smb_share_t, next)); + + /* First retrieve a list of all shares, without info */ + /* CMD: net conf listshares */ + ret = snprintf(cmd, sizeof (cmd), "%s -S %s conf listshares", + NET_CMD_PATH, NET_CMD_ARG_HOST); + if (ret < 0 || ret >= sizeof (cmd)) return (SA_SYSTEM_ERR); - /* Go through the directory, looking for shares */ - while ((directory = readdir(shares_dir))) { - if (directory->d_name[0] == '.') - continue; - - snprintf(file_path, sizeof (file_path), - "%s/%s", SHARE_DIR, directory->d_name); + sharesmb_temp_fp = popen(cmd, "r"); + if (sharesmb_temp_fp == NULL) + return (SA_SYSTEM_ERR); - if (stat(file_path, &eStat) == -1) { - rc = SA_SYSTEM_ERR; - goto out; + while (fgets(buffer, sizeof (buffer), sharesmb_temp_fp) != 0) { + /* Trim trailing new-line character(s). */ + buffer_len = strlen(buffer); + while (buffer_len > 0) { + buffer_len--; + if (buffer[buffer_len] == '\r' || + buffer[buffer_len] == '\n') { + buffer[buffer_len] = 0; + } else + break; } - if (!S_ISREG(eStat.st_mode)) - continue; + if (strcmp(buffer, "global") == 0) + continue; /* Not a share */ - if ((share_file_fp = fopen(file_path, "r")) == NULL) { - rc = SA_SYSTEM_ERR; - goto out; - } + /* Get the detailed info for the share */ + share_info = smb_retrieve_share_info(buffer); - name = strdup(directory->d_name); - if (name == NULL) { - rc = SA_NO_MEMORY; - goto out; - } +#ifdef DEBUG + fprintf(stderr, "smb_retrieve_shares: name='%s', " + "path='%s', comment='%s', guest_ok=%d, " + "writeable=%d\n", + share_info->name, share_info->path, + share_info->comment, share_info->guest_ok, + share_info->writeable); +#endif - while (fgets(line, sizeof (line), share_file_fp)) { - if (line[0] == '#') - continue; + /* Append the share to the list of new shares */ + list_insert_tail(&all_smb_shares_list, share_info); + } - /* Trim trailing new-line character(s). */ - while (line[strlen(line) - 1] == '\r' || - line[strlen(line) - 1] == '\n') - line[strlen(line) - 1] = '\0'; + if (pclose(sharesmb_temp_fp) != 0) + fprintf(stderr, "Failed to pclose stream\n"); - /* Split the line in two, separated by '=' */ - token = strchr(line, '='); - if (token == NULL) - continue; + return (SA_OK); +} - key = line; - value = token + 1; - *token = '\0'; +/* + * Validates share option(s). + */ +static int +smb_get_shareopts_cb(const char *key, const char *value, void *cookie) +{ + char *dup_value; + smb_share_t *opts = (smb_share_t *)cookie; - dup_value = strdup(value); - if (dup_value == NULL) { - rc = SA_NO_MEMORY; - goto out; - } + if (strcmp(key, "on") == 0) + return (SA_OK); + + /* guest_ok and guestok is the same */ + if (strcmp(key, "guestok") == 0) + key = "guest_ok"; + + /* Verify all options */ + if (strcmp(key, "name") != 0 && + strcmp(key, "comment") != 0 && + strcmp(key, "writeable") != 0 && + strcmp(key, "guest_ok") != 0) + return (SA_SYNTAX_ERR); + + dup_value = strdup(value); + if (dup_value == NULL) + return (SA_NO_MEMORY); + + + /* Get share option values */ + if (strcmp(key, "name") == 0) { + strncpy(opts->name, dup_value, sizeof (opts->name)); + opts->name [sizeof (opts->name)-1] = '\0'; + } + + if (strcmp(key, "comment") == 0) { + strncpy(opts->comment, dup_value, sizeof (opts->comment)); + opts->comment[sizeof (opts->comment) - 1] = '\0'; + } + + if (strcmp(key, "writeable") == 0) { + if (strcmp(dup_value, "y") == 0 || + strcmp(dup_value, "yes") == 0 || + strcmp(dup_value, "true") == 0) + opts->writeable = B_TRUE; + else + opts->writeable = B_FALSE; + } + + if (strcmp(key, "guest_ok") == 0) { + if (strcmp(dup_value, "y") == 0 || + strcmp(dup_value, "yes") == 0 || + strcmp(dup_value, "true") == 0) + opts->guest_ok = B_TRUE; + else + opts->guest_ok = B_FALSE; + } + + return (SA_OK); +} + +/* + * Takes a string containing share options (e.g. "name=Whatever,guest_ok=n") + * and converts them to a NULL-terminated array of options. + */ +static int +smb_get_shareopts(sa_share_impl_t impl_share, const char *shareopts, + smb_share_t **opts) +{ + char *pos, name[SMB_NAME_MAX]; + int rc, ret; + smb_share_t *new_opts; - if (strcmp(key, "path") == 0) - path = dup_value; - if (strcmp(key, "comment") == 0) - comment = dup_value; - if (strcmp(key, "guest_ok") == 0) - guest_ok = dup_value; - - if (path == NULL || comment == NULL || guest_ok == NULL) - continue; /* Incomplete share definition */ - else { - shares = (smb_share_t *) - malloc(sizeof (smb_share_t)); - if (shares == NULL) { - rc = SA_NO_MEMORY; - goto out; - } - - strncpy(shares->name, name, - sizeof (shares->name)); - shares->name [sizeof (shares->name) - 1] = '\0'; - - strncpy(shares->path, path, - sizeof (shares->path)); - shares->path [sizeof (shares->path) - 1] = '\0'; - - strncpy(shares->comment, comment, - sizeof (shares->comment)); - shares->comment[sizeof (shares->comment)-1] = - '\0'; - - shares->guest_ok = atoi(guest_ok); - - shares->next = new_shares; - new_shares = shares; - - name = NULL; - path = NULL; - comment = NULL; - guest_ok = NULL; + assert(opts != NULL); + *opts = NULL; + + /* Set defaults */ + new_opts = (smb_share_t *) malloc(sizeof (smb_share_t)); + if (new_opts == NULL) + return (SA_NO_MEMORY); + + if (impl_share && impl_share->dataset) { + strncpy(name, impl_share->dataset, sizeof (name)); + name [sizeof (name)-1] = '\0'; + + /* Support ZFS share name regexp '[[:alnum:]_-.: ]' */ + pos = name; + while (*pos != '\0') { + switch (*pos) { + case '/': + case '-': + case ':': + case ' ': + *pos = '_'; } + + ++pos; } -out: - if (share_file_fp != NULL) - fclose(share_file_fp); + strncpy(new_opts->name, name, sizeof (name)); + new_opts->name [sizeof (new_opts->name)-1] = '\0'; - free(name); - free(path); - free(comment); - free(guest_ok); + ret = snprintf(new_opts->comment, sizeof (new_opts->comment), + "Dataset name: %s", impl_share->dataset); + if (ret < 0 || ret >= sizeof (new_opts->comment)) + return (SA_SYSTEM_ERR); + } else { + new_opts->name[0] = '\0'; + new_opts->comment[0] = '\0'; } - closedir(shares_dir); - smb_shares = new_shares; + new_opts->writeable = B_TRUE; + new_opts->guest_ok = B_TRUE; + *opts = new_opts; + + rc = foreach_shareopt(shareopts, smb_get_shareopts_cb, *opts); + if (rc != SA_OK) { + free(*opts); + *opts = NULL; + } return (rc); } @@ -198,53 +392,84 @@ smb_retrieve_shares(void) * Used internally by smb_enable_share to enable sharing for a single host. */ static int -smb_enable_share_one(const char *sharename, const char *sharepath) +smb_enable_share_one(sa_share_impl_t impl_share) { - char *argv[10], *pos; - char name[SMB_NAME_MAX], comment[SMB_COMMENT_MAX]; + char *argv[11], *shareopts; + smb_share_t *opts; int rc; - /* Support ZFS share name regexp '[[:alnum:]_-.: ]' */ - strncpy(name, sharename, sizeof (name)); - name [sizeof (name)-1] = '\0'; - - pos = name; - while (*pos != '\0') { - switch (*pos) { - case '/': - case '-': - case ':': - case ' ': - *pos = '_'; - } +#ifdef DEBUG + fprintf(stderr, "smb_enable_share_one: dataset=%s, path=%s\n", + impl_share->dataset, impl_share->sharepath); +#endif - ++pos; - } + opts = (smb_share_t *) malloc(sizeof (smb_share_t)); + if (opts == NULL) + return (SA_NO_MEMORY); - /* - * CMD: net -S NET_CMD_ARG_HOST usershare add Test1 /share/Test1 \ - * "Comment" "Everyone:F" - */ - snprintf(comment, sizeof (comment), "Comment: %s", sharepath); + /* Get any share options */ + shareopts = FSINFO(impl_share, smb_fstype)->shareopts; + rc = smb_get_shareopts(impl_share, shareopts, &opts); + if (rc < 0) { + free(opts); + return (SA_SYSTEM_ERR); + } - argv[0] = NET_CMD_PATH; - argv[1] = (char *)"-S"; - argv[2] = NET_CMD_ARG_HOST; - argv[3] = (char *)"usershare"; - argv[4] = (char *)"add"; - argv[5] = (char *)name; - argv[6] = (char *)sharepath; - argv[7] = (char *)comment; - argv[8] = "Everyone:F"; - argv[9] = NULL; - - rc = libzfs_run_process(argv[0], argv, 0); - if (rc < 0) +#ifdef DEBUG + fprintf(stderr, "smb_enable_share_one: shareopts=%s, name=%s, " + "comment=\"%s\", writeable=%d, guest_ok=%d\n", shareopts, + opts->name, opts->comment, opts->writeable, opts->guest_ok); +#endif + + /* ====== */ + /* PART 1 - do the (inital) share. */ + /* CMD: net -S NET_CMD_ARG_HOST conf addshare \ */ + /* [writeable={y|n} [guest_ok={y|n} []] */ + argv[0] = NET_CMD_PATH; + argv[1] = (char *)"-S"; + argv[2] = NET_CMD_ARG_HOST; + argv[3] = (char *)"conf"; + argv[4] = (char *)"addshare"; + argv[5] = (char *)opts->name; + argv[6] = (char *)impl_share->sharepath; + if (opts->writeable) + argv[7] = (char *)"writeable=y"; + else + argv[7] = (char *)"writeable=n"; + if (opts->guest_ok) + argv[8] = (char *)"guest_ok=y"; + else + argv[8] = (char *)"guest_ok=n"; + argv[9] = (char *)opts->comment; + argv[10] = NULL; + +#ifdef DEBUG + int i; + fprintf(stderr, "CMD: "); + for (i = 0; i < 10; i++) + fprintf(stderr, "%s ", argv[i]); + fprintf(stderr, "\n"); +#endif + + rc = libzfs_run_process(argv[0], argv, STDERR_VERBOSE); + if (rc != 0) return (SA_SYSTEM_ERR); - /* Reload the share file */ - (void) smb_retrieve_shares(); + /* ====== */ + /* PART 2 - Run local update script. */ + if (access(EXTRA_SMBFS_SHARE_SCRIPT, X_OK) == 0) { + argv[0] = (char *)EXTRA_SMBFS_SHARE_SCRIPT; + argv[1] = opts->name; + argv[2] = NULL; + + rc = libzfs_run_process(argv[0], argv, STDERR_VERBOSE); + if (rc != 0) { + free(opts); + return (SA_SYSTEM_ERR); + } + } + free(opts); return (SA_OK); } @@ -267,8 +492,7 @@ smb_enable_share(sa_share_impl_t impl_share) return (SA_OK); /* Magic: Enable (i.e., 'create new') share */ - return (smb_enable_share_one(impl_share->dataset, - impl_share->sharepath)); + return (smb_enable_share_one(impl_share)); } /* @@ -280,17 +504,30 @@ smb_disable_share_one(const char *sharename) int rc; char *argv[7]; - /* CMD: net -S NET_CMD_ARG_HOST usershare delete Test1 */ +#ifdef DEBUG + fprintf(stderr, "smb_disable_share_one: Disabling share %s\n", + sharename); +#endif + + /* CMD: net -S NET_CMD_ARG_HOST conf delshare Test1 */ argv[0] = NET_CMD_PATH; argv[1] = (char *)"-S"; argv[2] = NET_CMD_ARG_HOST; - argv[3] = (char *)"usershare"; - argv[4] = (char *)"delete"; + argv[3] = (char *)"conf"; + argv[4] = (char *)"delshare"; argv[5] = strdup(sharename); argv[6] = NULL; - rc = libzfs_run_process(argv[0], argv, 0); - if (rc < 0) +#ifdef DEBUG + int i; + fprintf(stderr, "CMD: "); + for (i = 0; i < 6; i++) + fprintf(stderr, "%s ", argv[i]); + fprintf(stderr, "\n"); +#endif + + rc = libzfs_run_process(argv[0], argv, STDERR_VERBOSE); + if (rc != 0) return (SA_SYSTEM_ERR); else return (SA_OK); @@ -302,7 +539,8 @@ smb_disable_share_one(const char *sharename) static int smb_disable_share(sa_share_impl_t impl_share) { - smb_share_t *shares = smb_shares; + int ret; + smb_share_t *share; if (!smb_available()) { /* @@ -312,11 +550,26 @@ smb_disable_share(sa_share_impl_t impl_share) return (SA_OK); } - while (shares != NULL) { - if (strcmp(impl_share->sharepath, shares->path) == 0) - return (smb_disable_share_one(shares->name)); - - shares = shares->next; + /* Retrieve the list of (possible) active shares */ + smb_retrieve_shares(); + for (share = list_head(&all_smb_shares_list); + share != NULL; + share = list_next(&all_smb_shares_list, share)) + { +#ifdef DEBUG + fprintf(stderr, "smb_disable_share: %s ?? %s (%s)\n", + impl_share->sharepath, share->path, share->name); +#endif + if (strcmp(impl_share->sharepath, share->path) == 0) { +#ifdef DEBUG + fprintf(stderr, "=> disable %s (%s)\n", share->name, + share->path); +#endif + if((ret = smb_disable_share_one(share->name)) + == SA_OK) + list_remove(&all_smb_shares_list, share); + return (ret); + } } return (SA_OK); @@ -328,11 +581,9 @@ smb_disable_share(sa_share_impl_t impl_share) static int smb_validate_shareopts(const char *shareopts) { - /* TODO: Accept 'name' and sec/acl (?) */ - if ((strcmp(shareopts, "off") == 0) || (strcmp(shareopts, "on") == 0)) - return (SA_OK); + smb_share_t *opts; - return (SA_SYNTAX_ERR); + return (smb_get_shareopts(NULL, shareopts, &opts)); } /* @@ -341,17 +592,26 @@ smb_validate_shareopts(const char *shareopts) static boolean_t smb_is_share_active(sa_share_impl_t impl_share) { + smb_share_t *share; + if (!smb_available()) return (B_FALSE); /* Retrieve the list of (possible) active shares */ smb_retrieve_shares(); - - while (smb_shares != NULL) { - if (strcmp(impl_share->sharepath, smb_shares->path) == 0) + for (share = list_head(&all_smb_shares_list); share != NULL; + share = list_next(&all_smb_shares_list, share)) + { +#ifdef DEBUG + fprintf(stderr, "smb_is_share_active: %s ?? %s\n", + impl_share->sharepath, share->path); +#endif + if (strcmp(impl_share->sharepath, share->path) == 0) { +#ifdef DEBUG + fprintf(stderr, "=> %s is active\n", share->name); +#endif return (B_TRUE); - - smb_shares = smb_shares->next; + } } return (B_FALSE); @@ -371,8 +631,8 @@ smb_update_shareopts(sa_share_impl_t impl_share, const char *resource, boolean_t needs_reshare = B_FALSE; char *old_shareopts; - if (!impl_share) - return (SA_SYSTEM_ERR); + if (impl_share->dataset == NULL) + return (B_FALSE); FSINFO(impl_share, smb_fstype)->active = smb_is_share_active(impl_share); @@ -427,15 +687,7 @@ static const sa_share_ops_t smb_shareops = { static boolean_t smb_available(void) { - struct stat statbuf; - - if (lstat(SHARE_DIR, &statbuf) != 0 || - !S_ISDIR(statbuf.st_mode)) - return (B_FALSE); - - if (access(NET_CMD_PATH, F_OK) != 0) - return (B_FALSE); - + /* If we got past libshare_smb_init(), then it is available! */ return (B_TRUE); } @@ -445,5 +697,24 @@ smb_available(void) void libshare_smb_init(void) { - smb_fstype = register_fstype("smb", &smb_shareops); + int rc; + char *argv[5]; + + if (access(NET_CMD_PATH, X_OK) == 0) { + /* The net command exists, now Check samba access */ + argv[0] = NET_CMD_PATH; + argv[1] = (char *)"-S"; + argv[2] = NET_CMD_ARG_HOST; + argv[3] = (char *)"time"; + argv[4] = NULL; + + rc = libzfs_run_process(argv[0], argv, 0); + if (rc == 255) + fprintf(stderr, "ERROR: %s can't talk to samba.\n", + NET_CMD_PATH); + else + smb_fstype = register_fstype("smb", &smb_shareops); + } else + fprintf(stderr, "ERROR: %s does not exist or not executable\n", + NET_CMD_PATH); } diff --git a/lib/libshare/smb.h b/lib/libshare/smb.h index 7a0c0fd162d5..76b68c89dd98 100644 --- a/lib/libshare/smb.h +++ b/lib/libshare/smb.h @@ -28,22 +28,24 @@ * references are hard to find. */ -#define SMB_NAME_MAX 255 -#define SMB_COMMENT_MAX 255 +#include -#define SHARE_DIR "/var/lib/samba/usershares" -#define NET_CMD_PATH "/usr/bin/net" -#define NET_CMD_ARG_HOST "127.0.0.1" +#define SMB_NAME_MAX 255 +#define SMB_COMMENT_MAX 255 + +#define NET_CMD_PATH "/usr/bin/net" +#define NET_CMD_ARG_HOST "127.0.0.1" +#define EXTRA_SMBFS_SHARE_SCRIPT "/sbin/zfs_share_smbfs" typedef struct smb_share_s { char name[SMB_NAME_MAX]; /* Share name */ char path[PATH_MAX]; /* Share path */ - char comment[SMB_COMMENT_MAX]; /* Share's comment */ + + char comment[SMB_COMMENT_MAX]; /* Share comment */ + boolean_t writeable; /* 'y' or 'n' */ boolean_t guest_ok; /* 'y' or 'n' */ - struct smb_share_s *next; + list_node_t next; } smb_share_t; -smb_share_t *smb_shares; - void libshare_smb_init(void); diff --git a/lib/libshare/zfs_share_smbfs b/lib/libshare/zfs_share_smbfs new file mode 100644 index 000000000000..5da83dd7647d --- /dev/null +++ b/lib/libshare/zfs_share_smbfs @@ -0,0 +1,16 @@ +#!/bin/sh + +# !!! THIS IS AN EXAMPLE !!! + +SHARE="$1" + +net conf setparm $SHARE 'force create mode' 0664 +net conf setparm $SHARE 'force directory mode' 0775 +net conf setparm $SHARE public yes +net conf setparm $SHARE available yes +net conf setparm $SHARE 'inherit acls' yes +net conf setparm $SHARE 'inherit owner' yes +net conf setparm $SHARE 'inherit permissions' yes +net conf setparm $SHARE 'write list' yes +net conf setparm $SHARE 'hide dot files' yes +net conf setparm $SHARE 'read only' yes diff --git a/lib/libzfs/libzfs_changelist.c b/lib/libzfs/libzfs_changelist.c index 01c237c37edd..88ac6c85ce5f 100644 --- a/lib/libzfs/libzfs_changelist.c +++ b/lib/libzfs/libzfs_changelist.c @@ -260,10 +260,19 @@ changelist_postfix(prop_changelist_t *clp) * if the filesystem is currently shared, so that we can * adopt any new options. */ - if (((sharenfs || sharesmb) && mounted) || shareiscsi) - errors += zfs_share(cn->cn_handle); + if (sharenfs && mounted) + errors += zfs_share_nfs(cn->cn_handle); else if (cn->cn_shared || clp->cl_waslegacy) - errors += zfs_unshare(cn->cn_handle); + errors += zfs_unshare_nfs(cn->cn_handle, NULL); + if (sharesmb && mounted) + errors += zfs_share_smb(cn->cn_handle); +// zfs_unshare_smb() is called twice if setting 'sharesmb=off' once 'somewhere +// else' and once here (this one AFTER the share have been unshared). This +// which makes this one fail! Don't know if this is correct, but it works to +// comment this... +// else if (cn->cn_shared || clp->cl_waslegacy) { +// errors += zfs_unshare_smb(cn->cn_handle, NULL); +// } } return (errors ? -1 : 0); diff --git a/man/man8/zfs.8 b/man/man8/zfs.8 index 3fbd6a008b6a..6a6439fce0b5 100644 --- a/man/man8/zfs.8 +++ b/man/man8/zfs.8 @@ -1278,41 +1278,128 @@ Example scripts (one for each iSCSI implementation) have been included with the .ne 2 .mk .na -\fB\fBsharesmb\fR=\fBon\fR | \fBoff\fR +\fB\fBsharesmb\fR=\fBon\fR | \fBoff\fR | \fIopts\fR\fR .ad .sp .6 .RS 4n -Controls whether the file system is shared by using \fBSamba USERSHARES\fR, and what options are to be used. Otherwise, the file system is automatically shared and unshared with the \fBzfs share\fR and \fBzfs unshare\fR commands. If the property is set to \fBon\fR, the \fBnet\fR(8) command is invoked to create a \fBUSERSHARE\fR. -.sp -Because \fBSMB\fR shares requires a resource name, a unique resource name is constructed from the dataset name. The constructed name is a copy of the dataset name except that the characters in the dataset name, which would be illegal in the resource name, are replaced with underscore (\fB_\fR) characters. The ZFS On Linux driver does not (yet) support additional options which might be availible in the Solaris version. +Controls whether the file system is shared by creating a \fBSamba registry share\fR, and what options are to be used. Otherwise, the file system is automatically shared and unshared with the \fBzfs share\fR and \fBzfs unshare\fR commands. If the property is set to \fBon\fR, the \fBnet\fR(8) command is invoked with default options (see below) to create a \fBREGISTRY SHARE\fR. .sp If the \fBsharesmb\fR property is set to \fBoff\fR, the file systems are unshared. .sp -In Linux, the share is created with the ACL (Access Control List) "Everyone:F" ("F" stands for "full permissions", ie. read and write permissions) and no guest access (which means samba must be able to authenticate a real user, system passwd/shadow, ldap or smbpasswd based) by default. This means that any additional access control (dissalow specific user specific access etc) must be done on the underlaying filesystem. +.sp +Because \fBSMB\fR shares requires a resource name, a unique resource name is constructed from the dataset name, unless the \fBname\fR option to \fBsharesmb\fR is used). The constructed name is a copy of the dataset name except that the characters in the dataset name, which would be illegal in the resource name, are replaced with underscore (\fB_\fR) characters. +.sp +Example: +.sp +.in +2 +If a filesystem is named: +.sp +.in +2 +tank/files/some/directory +.in -2 +.sp +the Samba share will be named: .sp .in +2 -Example to mount a SMB filesystem shared through ZFS (share/tmp): +tank_files_some_directory +.in -2 +.in -2 +.sp +Also, the comment for the share would be (if autogenerated and not specified using the \fBcomment\fR option): +.sp +.in +2 +Comment: tank/files/some/directory +.in -2 +.sp +In Linux, the share is created writeable by default. This means that any additional access control (disallow specific user specific access etc) must be done on the underlaying filesystem. If a read-only share is wanted, give \fBsharesmb\fR the value \fIwriteable=n\fR. +.sp +The "guest_ok" option has the same effect as the parameter of the same name in smb.conf, in that it allows guest access to this user defined share. +.sp +.na +\fBExample to mount a SMB filesystem shared through ZFS:\fR +.sp .mk Note that a user and his/her password \fBmust\fR be given! .sp .in +2 -smbmount //127.0.0.1/share_tmp /mnt/tmp -o user=workgroup/turbo,password=obrut,uid=1000 -.in -2 +smbmount //127.0.0.1/tank_files_some_directory /mnt/tmp -o user=workgroup/turbo,password=obrut,uid=1000 .in -2 .sp -.ne 2 +.ne .mk .na -\fBMinimal /etc/samba/smb.conf configuration\fR +\fBopts\fR can be any one of: +.sp +.TP +.ie t \(bu +.el o +\fIname\fR Share name. Can not contain spaces or invalid characters (see \fBsmb.conf(5)\fR). +.RE +.RS +4 +.TP +.ie t \(bu +.el o +\fIcomment\fR Share comment. If the comment contains a space, it needs to be enclosed in "". +.sp +Example: comment="Printer drivers" +.RE +.RS +4 +.TP +.ie t \(bu +.el o +\fIwriteable\fR Share writeable or read-only. Accepts 'y' or 'n' (as well as 'yes' and 'true'). +.RE +.RS +4 +.TP +.ie t \(bu +.el o +\fIguest_ok\fR Allow guest access. Accepts 'y' or 'n' (as well as 'yes' and 'true'). +.RE +.RE +.sp +.in +4 +.na +\fBMinimal smb.conf configuration\fR +.sp +1. Samba will need to listen to 'localhost' (127.0.0.1) for the zfs utilities to communitate with samba. This is the default behavior for most Linux distributions. +.sp +2. Samba must be able to authenticate a user. This can be done in a number of ways, depending on if using the system password file, LDAP or the Samba specific smbpasswd file. How to do this is outside the scope of this manual. Please refer to the smb.conf(5) manpage for more information. +.sp +3. To be allowed to use \fBregistry shares\fR in samba, the global configuration option \fIregistry shares\fR needs to be set to \fByes\fR: +.sp +.in +2 +registry shares = yes +.in -2 +.sp +The module will use the optional script/binary named \fB/sbin/zfs_share_smbfs\fR, if it exists and is executable, after \fBnet\fR have shared the volume. The only parameter to this script/binary is the share name and this script is intended to allow administrators to add custom commands to be done on the share. +The module will 'execute and forget'. Meaning, it will not care about exit code nor any output it gives. So if the script/binary fails for some reason, it is up to it to catch this. The ZFS module will not intervene. +.RE +.sp +.in +4 +.na +\fBExample to modify share after ZFS have shared it\fR +.sp +To set the share not writable (read-only): +.na .sp .in +2 -* Samba will need to listen to 'localhost' (127.0.0.1) for the zfs utilities to communitate with samba. This is the default behavior for most Linux distributions. +net conf setparm $share 'read only' yes +.in -2 +.na .sp -* Samba must be able to authenticate a user. This can be done in a number of ways, depending on if using the system password file, LDAP or the Samba specific smbpasswd file. How to do this is outside the scope of this manual. Please refer to the smb.conf(5) manpage for more information. +See \fBsmb.conf(5)\fR for parameters to use. Any parameter usable in the smb.conf file can be used with the \fBnet conf setparm\fR command. +.na .sp -* See the \fBUSERSHARE\fR section of the \fBsmb.conf\fR(5) man page for all configuration options in case you need to modify any options to the share afterwards. Do note that any changes done with the 'net' command will be undone if the share is every unshared (such as at a reboot etc). In the future, ZoL will be able to set specific options directly using sharesmb=