From a7a4ee439dc1cf262b4da9fbcb38a2f69c62744c Mon Sep 17 00:00:00 2001 From: Martin Schwenke Date: Thu, 12 Jul 2018 13:20:55 +1000 Subject: [PATCH] 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 Reviewed-by: Amitay Isaacs --- ctdb/common/event_script.c | 246 ++++++++++++++++++++++ ctdb/common/event_script.h | 72 +++++++ ctdb/tests/cunit/event_script_test_001.sh | 125 +++++++++++ ctdb/tests/src/event_script_test.c | 119 +++++++++++ ctdb/wscript | 4 +- 5 files changed, 565 insertions(+), 1 deletion(-) create mode 100644 ctdb/common/event_script.c create mode 100644 ctdb/common/event_script.h create mode 100755 ctdb/tests/cunit/event_script_test_001.sh create mode 100644 ctdb/tests/src/event_script_test.c 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 . +*/ + +#include "replace.h" +#include "system/filesys.h" +#include "system/dir.h" +#include "system/glob.h" + +#include + +#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 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 . +*/ + +#ifndef __CTDB_SCRIPT_H__ +#define __CTDB_SCRIPT_H__ + +#include "replace.h" +#include "system/filesys.h" + +#include + +/** + * @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 <. +*/ + +#include "replace.h" + +#include +#include + +#include + +#include "common/event_script.c" + +static void usage(const char *prog) +{ + fprintf(stderr, + "Usage: %s list \n", + prog); + fprintf(stderr, + " %s chmod enable \n", + prog); + fprintf(stderr, + " %s chmod diable \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: -- 2.34.1