diff options
author | Martin Schwenke <martin@meltin.net> | 2018-07-12 13:20:55 +1000 |
---|---|---|
committer | Karolin Seeger <kseeger@samba.org> | 2018-07-31 12:36:28 +0200 |
commit | 4cce86e872b10f387988dbf6c06303c55bfb6018 (patch) | |
tree | 5d0fbe3fa21e0b418b7a5ef12026a7333a30cbca | |
parent | 34aba6f9ef373b1e654e13f0bbe802087c1a4ec6 (diff) | |
download | samba-4cce86e872b10f387988dbf6c06303c55bfb6018.tar.gz |
ctdb-common: Factor out basic script abstraction
Provides for listing of scripts and chmod enable/disable.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=13551
Signed-off-by: Martin Schwenke <martin@meltin.net>
Reviewed-by: Amitay Isaacs <amitay@gmail.com>
(cherry picked from commit a7a4ee439dc1cf262b4da9fbcb38a2f69c62744c)
-rw-r--r-- | ctdb/common/event_script.c | 246 | ||||
-rw-r--r-- | ctdb/common/event_script.h | 72 | ||||
-rwxr-xr-x | ctdb/tests/cunit/event_script_test_001.sh | 125 | ||||
-rw-r--r-- | ctdb/tests/src/event_script_test.c | 119 | ||||
-rw-r--r-- | ctdb/wscript | 4 |
5 files changed, 565 insertions, 1 deletions
diff --git a/ctdb/common/event_script.c b/ctdb/common/event_script.c new file mode 100644 index 00000000000..8978d1452c0 --- /dev/null +++ b/ctdb/common/event_script.c @@ -0,0 +1,246 @@ +/* + Low level event script handling + + Copyright (C) Amitay Isaacs 2017 + Copyright (C) Martin Schwenke 2018 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" +#include "system/dir.h" +#include "system/glob.h" + +#include <talloc.h> + +#include "common/event_script.h" + +static int script_filter(const struct dirent *de) +{ + int ret; + + /* Match a script pattern */ + ret = fnmatch("[0-9][0-9].*.script", de->d_name, 0); + if (ret == 0) { + return 1; + } + + return 0; +} + +int event_script_get_list(TALLOC_CTX *mem_ctx, + const char *script_dir, + struct event_script_list **out) +{ + struct dirent **namelist = NULL; + struct event_script_list *script_list = NULL; + size_t ds_len; + int count, ret; + int i; + + count = scandir(script_dir, &namelist, script_filter, alphasort); + if (count == -1) { + ret = errno; + goto done; + } + + script_list = talloc_zero(mem_ctx, struct event_script_list); + if (script_list == NULL) { + goto nomem; + } + + if (count == 0) { + ret = 0; + *out = script_list; + goto done; + } + + script_list->num_scripts = count; + script_list->script = talloc_zero_array(script_list, + struct event_script *, + count); + if (script_list->script == NULL) { + goto nomem; + } + + ds_len = strlen(".script"); + for (i = 0; i < count; i++) { + struct event_script *s; + struct stat statbuf; + + s = talloc_zero(script_list->script, struct event_script); + if (s == NULL) { + goto nomem; + } + + script_list->script[i] = s; + + s->name = talloc_strndup(script_list->script, + namelist[i]->d_name, + strlen(namelist[i]->d_name) - ds_len); + if (s->name == NULL) { + goto nomem; + } + + s->path = talloc_asprintf(script_list->script, + "%s/%s", + script_dir, + namelist[i]->d_name); + if (s->path == NULL) { + goto nomem; + } + + ret = stat(s->path, &statbuf); + if (ret == 0) { + /* + * If ret != 0 this is either a dangling + * symlink or it has just disappeared. Either + * way, it isn't executable. See the note + * below about things that have disappeared. + */ + if (statbuf.st_mode & S_IXUSR) { + s->enabled = true; + } + } + } + + *out = script_list; + return 0; + +nomem: + ret = ENOMEM; + talloc_free(script_list); + +done: + if (namelist != NULL && count != -1) { + for (i=0; i<count; i++) { + free(namelist[i]); + } + free(namelist); + } + + return ret; +} + +int event_script_chmod(const char *script_dir, + const char *script_name, + bool enable) +{ + const char *dot_script = ".script"; + size_t ds_len = strlen(dot_script); + size_t sn_len = strlen(script_name); + DIR *dirp; + struct dirent *de; + char buf[PATH_MAX]; + const char *script_file; + int ret, new_mode; + char filename[PATH_MAX]; + struct stat st; + bool found; + ino_t found_inode; + int fd = -1; + + /* Allow script_name to already have ".script" suffix */ + if (sn_len > ds_len && + strcmp(&script_name[sn_len - ds_len], dot_script) == 0) { + script_file = script_name; + } else { + ret = snprintf(buf, sizeof(buf), "%s.script", script_name); + if (ret >= sizeof(buf)) { + return ENAMETOOLONG; + } + script_file = buf; + } + + dirp = opendir(script_dir); + if (dirp == NULL) { + return errno; + } + + found = false; + while ((de = readdir(dirp)) != NULL) { + if (strcmp(de->d_name, script_file) == 0) { + /* check for valid script names */ + ret = script_filter(de); + if (ret == 0) { + closedir(dirp); + return EINVAL; + } + + found = true; + found_inode = de->d_ino; + break; + } + } + closedir(dirp); + + if (! found) { + return ENOENT; + } + + ret = snprintf(filename, + sizeof(filename), + "%s/%s", + script_dir, + script_file); + if (ret >= sizeof(filename)) { + return ENAMETOOLONG; + } + + fd = open(filename, O_RDWR); + if (fd == -1) { + ret = errno; + goto done; + } + + ret = fstat(fd, &st); + if (ret != 0) { + ret = errno; + goto done; + } + + /* + * If the directory entry inode number doesn't match the one + * returned by fstat() then this is probably a symlink, so the + * caller should not be calling this function. Note that this + * is a cheap sanity check to catch most programming errors. + * This doesn't cost any extra system calls but can still miss + * the unlikely case where the symlink is to a file on a + * different filesystem with the same inode number as the + * symlink. + */ + if (found && found_inode != st.st_ino) { + ret = EINVAL; + goto done; + } + + if (enable) { + new_mode = st.st_mode | (S_IXUSR | S_IXGRP | S_IXOTH); + } else { + new_mode = st.st_mode & ~(S_IXUSR | S_IXGRP | S_IXOTH); + } + + ret = fchmod(fd, new_mode); + if (ret != 0) { + ret = errno; + goto done; + } + +done: + if (fd != -1) { + close(fd); + } + return ret; +} diff --git a/ctdb/common/event_script.h b/ctdb/common/event_script.h new file mode 100644 index 00000000000..bf5a8fdf9a2 --- /dev/null +++ b/ctdb/common/event_script.h @@ -0,0 +1,72 @@ +/* + Low level event script handling + + Copyright (C) Amitay Isaacs 2017 + Copyright (C) Martin Schwenke 2018 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __CTDB_SCRIPT_H__ +#define __CTDB_SCRIPT_H__ + +#include "replace.h" +#include "system/filesys.h" + +#include <talloc.h> + +/** + * @file script.h + * + * @brief Script listing and manipulation + */ + + +struct event_script { + char *name; + char *path; + bool enabled; +}; + +struct event_script_list { + unsigned int num_scripts; + struct event_script **script; +}; + + +/** + * @brief Retrieve a list of scripts + * + * @param[in] mem_ctx Talloc memory context + * @param[in] script_dir Directory containing scripts + * @param[out] out List of scripts + * @return 0 on success, errno on failure + */ +int event_script_get_list(TALLOC_CTX *mem_ctx, + const char *script_dir, + struct event_script_list **out); + +/** + * @brief Make a script executable or not executable + * + * @param[in] script_dir Directory containing script + * @param[in] script_name Name of the script to enable + * @param[in] executable True if script should be made executable + * @return 0 on success, errno on failure + */ +int event_script_chmod(const char *script_dir, + const char *script_name, + bool executable); + +#endif /* __CTDB_SCRIPT_H__ */ diff --git a/ctdb/tests/cunit/event_script_test_001.sh b/ctdb/tests/cunit/event_script_test_001.sh new file mode 100755 index 00000000000..9a264122e9a --- /dev/null +++ b/ctdb/tests/cunit/event_script_test_001.sh @@ -0,0 +1,125 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +scriptdir="${TEST_VAR_DIR}/cunit/scriptdir" +mkdir -p "${scriptdir}" + +test_cleanup "rm -rf ${scriptdir}" + +# Invalid path +invalid="${scriptdir}/notfound" +ok <<EOF +Script list ${invalid} failed with result=$(errcode ENOENT) +EOF +unit_test event_script_test list "${invalid}" + +# Empty directory +ok <<EOF +No scripts found +EOF +unit_test event_script_test list "$scriptdir" + +# Invalid script, doesn't end in ".script" +touch "${scriptdir}/prog" + +ok <<EOF +No scripts found +EOF +unit_test event_script_test list "$scriptdir" + +# Is not found because enabling "prog" actually looks for "prog.script" +ok <<EOF +Script enable ${scriptdir} prog completed with result=$(errcode ENOENT) +EOF +unit_test event_script_test enable "$scriptdir" "prog" + +required_result 1 <<EOF +EOF +unit_test test -x "${scriptdir}/prog" + +# Is not found because enabling "prog" actually looks for "prog.script" +ok <<EOF +Script disable ${scriptdir} prog completed with result=$(errcode ENOENT) +EOF +unit_test event_script_test disable "$scriptdir" "prog" + +# Valid script +touch "$scriptdir/11.foo.script" + +ok <<EOF +11.foo +EOF +unit_test event_script_test list "$scriptdir" + +ok <<EOF +Script enable ${scriptdir} 11.foo completed with result=0 +EOF +unit_test event_script_test enable "$scriptdir" "11.foo" + +ok <<EOF +EOF +unit_test test -x "${scriptdir}/11.foo.script" + +ok <<EOF +Script disable ${scriptdir} 11.foo.script completed with result=0 +EOF +unit_test event_script_test disable "$scriptdir" "11.foo.script" + +required_result 1 <<EOF +EOF +unit_test test -x "${scriptdir}/11.foo.script" + +# Multiple scripts +touch "${scriptdir}/22.bar.script" + +ok <<EOF +11.foo +22.bar +EOF +unit_test event_script_test list "$scriptdir" + +# Symlink to existing file +ln -s "${scriptdir}/prog" "${scriptdir}/33.link.script" + +ok <<EOF +11.foo +22.bar +33.link +EOF +unit_test event_script_test list "$scriptdir" + +ok <<EOF +Script enable ${scriptdir} 33.link completed with result=$(errcode EINVAL) +EOF +unit_test event_script_test enable "$scriptdir" "33.link" + + +ok <<EOF +Script disable ${scriptdir} 33.link.script completed with result=$(errcode EINVAL) +EOF +unit_test event_script_test disable "$scriptdir" "33.link.script" + +# Dangling symlink +rm "${scriptdir}/33.link.script" +ln -s "${scriptdir}/nosuchfile" "${scriptdir}/33.link.script" + +ok <<EOF +11.foo +22.bar +33.link +EOF +unit_test event_script_test list "$scriptdir" + +ok <<EOF +Script enable ${scriptdir} 33.link completed with result=$(errcode ENOENT) +EOF +unit_test event_script_test enable "$scriptdir" "33.link" + + +ok <<EOF +Script disable ${scriptdir} 33.link.script completed with result=$(errcode ENOENT) +EOF +unit_test event_script_test disable "$scriptdir" "33.link.script" + +exit 0 diff --git a/ctdb/tests/src/event_script_test.c b/ctdb/tests/src/event_script_test.c new file mode 100644 index 00000000000..73c974dbd39 --- /dev/null +++ b/ctdb/tests/src/event_script_test.c @@ -0,0 +1,119 @@ +/* + Low level event script handling tests + + Copyright (C) Martin Schwenke 2018 + + Based on run_event_test.c: + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include <popt.h> +#include <talloc.h> + +#include <assert.h> + +#include "common/event_script.c" + +static void usage(const char *prog) +{ + fprintf(stderr, + "Usage: %s list <scriptdir>\n", + prog); + fprintf(stderr, + " %s chmod enable <scriptdir> <scriptname>\n", + prog); + fprintf(stderr, + " %s chmod diable <scriptdir> <scriptname>\n", + prog); +} + +static void do_list(TALLOC_CTX *mem_ctx, int argc, const char **argv) +{ + struct event_script_list *script_list = NULL; + int ret, i; + + if (argc != 3) { + usage(argv[0]); + exit(1); + } + + ret = event_script_get_list(mem_ctx, argv[2], &script_list); + if (ret != 0) { + printf("Script list %s failed with result=%d\n", argv[2], ret); + return; + } + + if (script_list == NULL || script_list->num_scripts == 0) { + printf("No scripts found\n"); + return; + } + + for (i=0; i < script_list->num_scripts; i++) { + struct event_script *s = script_list->script[i]; + printf("%s\n", s->name); + } +} + +static void do_chmod(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + bool enable) +{ + int ret; + + if (argc != 4) { + usage(argv[0]); + exit(1); + } + + ret = event_script_chmod(argv[2], argv[3], enable); + + printf("Script %s %s %s completed with result=%d\n", + argv[1], argv[2], argv[3], ret); +} + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + + if (argc < 3) { + usage(argv[0]); + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "talloc_new() failed\n"); + exit(1); + } + + if (strcmp(argv[1], "list") == 0) { + do_list(mem_ctx, argc, argv); + } else if (strcmp(argv[1], "enable") == 0) { + do_chmod(mem_ctx, argc, argv, true); + } else if (strcmp(argv[1], "disable") == 0) { + do_chmod(mem_ctx, argc, argv, false); + } else { + fprintf(stderr, "Invalid command %s\n", argv[2]); + usage(argv[0]); + } + + talloc_free(mem_ctx); + exit(0); +} diff --git a/ctdb/wscript b/ctdb/wscript index 6e69e499985..05044cebdeb 100644 --- a/ctdb/wscript +++ b/ctdb/wscript @@ -402,7 +402,8 @@ def build(bld): pkt_read.c pkt_write.c comm.c logging.c rb_tree.c tunable.c pidfile.c run_proc.c - hash_count.c run_event.c + hash_count.c + run_event.c event_script.c sock_client.c version.c cmdline.c path.c conf.c line.c '''), @@ -869,6 +870,7 @@ def build(bld): 'cmdline_test', 'conf_test', 'line_test', + 'event_script_test', ] for target in ctdb_unit_tests: |