summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Schwenke <martin@meltin.net>2018-07-12 13:20:55 +1000
committerKarolin Seeger <kseeger@samba.org>2018-07-31 12:36:28 +0200
commit4cce86e872b10f387988dbf6c06303c55bfb6018 (patch)
tree5d0fbe3fa21e0b418b7a5ef12026a7333a30cbca
parent34aba6f9ef373b1e654e13f0bbe802087c1a4ec6 (diff)
downloadsamba-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.c246
-rw-r--r--ctdb/common/event_script.h72
-rwxr-xr-xctdb/tests/cunit/event_script_test_001.sh125
-rw-r--r--ctdb/tests/src/event_script_test.c119
-rw-r--r--ctdb/wscript4
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: