mirror of
https://github.com/samba-team/samba.git
synced 2024-12-23 17:34:34 +03:00
2b6f1a8ee6
This makes the code more explicit and makes testing easier due to less dependencies. Signed-off-by: Martin Schwenke <martin@meltin.net> Reviewed-by: Amitay Isaacs <amitay@gmail.com>
383 lines
8.7 KiB
C
383 lines
8.7 KiB
C
/*
|
|
CTDB cluster mutex handling
|
|
|
|
Copyright (C) Andrew Tridgell 2007
|
|
Copyright (C) Ronnie Sahlberg 2007
|
|
Copyright (C) Martin Schwenke 2016
|
|
|
|
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/network.h"
|
|
#include "system/filesys.h"
|
|
|
|
#include <tevent.h>
|
|
|
|
#include "lib/util/debug.h"
|
|
#include "lib/util/time.h"
|
|
#include "lib/util/strv.h"
|
|
#include "lib/util/strv_util.h"
|
|
#include "lib/util/sys_rw.h"
|
|
#include "lib/util/blocking.h"
|
|
|
|
#include "ctdb_private.h"
|
|
|
|
#include "ctdb_cluster_mutex.h"
|
|
|
|
struct ctdb_cluster_mutex_handle {
|
|
struct ctdb_context *ctdb;
|
|
cluster_mutex_handler_t handler;
|
|
void *private_data;
|
|
cluster_mutex_lost_handler_t lost_handler;
|
|
void *lost_data;
|
|
int fd[2];
|
|
struct tevent_timer *te;
|
|
struct tevent_fd *fde;
|
|
pid_t child;
|
|
struct timeval start_time;
|
|
bool have_response;
|
|
};
|
|
|
|
static void cluster_mutex_timeout(struct tevent_context *ev,
|
|
struct tevent_timer *te,
|
|
struct timeval t, void *private_data)
|
|
{
|
|
struct ctdb_cluster_mutex_handle *h =
|
|
talloc_get_type(private_data, struct ctdb_cluster_mutex_handle);
|
|
double latency = timeval_elapsed(&h->start_time);
|
|
|
|
if (h->handler != NULL) {
|
|
h->handler('2', latency, h->private_data);
|
|
}
|
|
}
|
|
|
|
|
|
/* When the handle is freed it causes any child holding the mutex to
|
|
* be killed, thus freeing the mutex */
|
|
static int cluster_mutex_destructor(struct ctdb_cluster_mutex_handle *h)
|
|
{
|
|
if (h->fd[0] != -1) {
|
|
h->fd[0] = -1;
|
|
}
|
|
ctdb_kill(h->ctdb, h->child, SIGTERM);
|
|
return 0;
|
|
}
|
|
|
|
/* this is called when the client process has completed ctdb_recovery_lock()
|
|
and has written data back to us through the pipe.
|
|
*/
|
|
static void cluster_mutex_handler(struct tevent_context *ev,
|
|
struct tevent_fd *fde,
|
|
uint16_t flags, void *private_data)
|
|
{
|
|
struct ctdb_cluster_mutex_handle *h=
|
|
talloc_get_type(private_data, struct ctdb_cluster_mutex_handle);
|
|
double latency = timeval_elapsed(&h->start_time);
|
|
char c = '0';
|
|
int ret;
|
|
|
|
/* Got response from child process so abort timeout */
|
|
TALLOC_FREE(h->te);
|
|
|
|
ret = sys_read(h->fd[0], &c, 1);
|
|
|
|
/* Don't call the handler more than once. It only exists to
|
|
* process the initial response from the helper. */
|
|
if (h->have_response) {
|
|
/* Only deal with EOF due to process exit. Silently
|
|
* ignore any other output. */
|
|
if (ret == 0) {
|
|
if (h->lost_handler != NULL) {
|
|
h->lost_handler(h->lost_data);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
h->have_response = true;
|
|
|
|
/* If the child wrote status then just pass it to the handler.
|
|
* If no status was written then this is an unexpected error
|
|
* so pass generic error code to handler. */
|
|
if (h->handler != NULL) {
|
|
h->handler(ret == 1 ? c : '3', latency, h->private_data);
|
|
}
|
|
}
|
|
|
|
static char cluster_mutex_helper[PATH_MAX+1] = "";
|
|
|
|
static bool cluster_mutex_helper_args_file(TALLOC_CTX *mem_ctx,
|
|
const char *argstring,
|
|
char ***argv)
|
|
{
|
|
struct stat st;
|
|
size_t size = sizeof(cluster_mutex_helper);
|
|
const char *t;
|
|
char **args = NULL;
|
|
int ret;
|
|
|
|
if (cluster_mutex_helper[0] != '\0') {
|
|
goto helper_done;
|
|
}
|
|
|
|
t = getenv("CTDB_CLUSTER_MUTEX_HELPER");
|
|
if (t != NULL) {
|
|
size_t len;
|
|
|
|
len = strlcpy(cluster_mutex_helper, t, size);
|
|
if (len >= size) {
|
|
DBG_ERR("error: CTDB_CLUSTER_MUTEX_HELPER too long\n");
|
|
exit(1);
|
|
}
|
|
} else {
|
|
ret = snprintf(cluster_mutex_helper,
|
|
size,
|
|
"%s/%s",
|
|
CTDB_HELPER_BINDIR,
|
|
"ctdb_mutex_fcntl_helper");
|
|
if (ret < 0 || (size_t)ret >= size) {
|
|
D_ERR("Unable to set cluster mutex helper - "
|
|
"path too long\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
ret = stat(cluster_mutex_helper, &st);
|
|
if (ret != 0) {
|
|
D_ERR("Unable to set cluster mutex helper \"%s\" - %s\n",
|
|
cluster_mutex_helper,
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
|
|
if ((st.st_mode & S_IXUSR) == 0) {
|
|
D_ERR("Unable to set cluster_mutex helper \"%s\" - "
|
|
"not executable\n",
|
|
cluster_mutex_helper);
|
|
exit(1);
|
|
}
|
|
|
|
D_NOTICE("Set cluster mutex helper to \"%s\"\n", cluster_mutex_helper);
|
|
|
|
helper_done:
|
|
|
|
/* Array includes default helper, file and NULL */
|
|
args = talloc_array(mem_ctx, char *, 3);
|
|
if (args == NULL) {
|
|
DBG_ERR("Memory allocation error\n");
|
|
return false;
|
|
}
|
|
|
|
args[0] = cluster_mutex_helper;
|
|
|
|
args[1] = talloc_strdup(args, argstring);
|
|
if (args[1] == NULL) {
|
|
DBG_ERR("Memory allocation error\n");
|
|
return false;
|
|
}
|
|
|
|
args[2] = NULL;
|
|
|
|
*argv = args;
|
|
return true;
|
|
}
|
|
|
|
static bool cluster_mutex_helper_args_cmd(TALLOC_CTX *mem_ctx,
|
|
const char *argstring,
|
|
char ***argv)
|
|
{
|
|
int i, ret, n;
|
|
char **args = NULL;
|
|
char *strv = NULL;
|
|
char *t = NULL;
|
|
|
|
ret = strv_split(mem_ctx, &strv, argstring, " \t");
|
|
if (ret != 0) {
|
|
D_ERR("Unable to parse mutex helper command \"%s\" (%s)\n",
|
|
argstring,
|
|
strerror(ret));
|
|
return false;
|
|
}
|
|
n = strv_count(strv);
|
|
if (n == 0) {
|
|
D_ERR("Mutex helper command is empty \"%s\"\n", argstring);
|
|
return false;
|
|
}
|
|
|
|
/* Extra slot for NULL */
|
|
args = talloc_array(mem_ctx, char *, n + 1);
|
|
if (args == NULL) {
|
|
DBG_ERR("Memory allocation error\n");
|
|
return false;
|
|
}
|
|
|
|
talloc_steal(args, strv);
|
|
|
|
t = NULL;
|
|
for (i = 0 ; i < n; i++) {
|
|
t = strv_next(strv, t);
|
|
args[i] = t;
|
|
}
|
|
|
|
args[n] = NULL;
|
|
|
|
*argv = args;
|
|
return true;
|
|
}
|
|
|
|
static bool cluster_mutex_helper_args(TALLOC_CTX *mem_ctx,
|
|
const char *argstring,
|
|
char ***argv)
|
|
{
|
|
bool ok;
|
|
|
|
if (argstring != NULL && argstring[0] == '!') {
|
|
ok = cluster_mutex_helper_args_cmd(mem_ctx, &argstring[1], argv);
|
|
} else {
|
|
ok = cluster_mutex_helper_args_file(mem_ctx, argstring, argv);
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
struct ctdb_cluster_mutex_handle *
|
|
ctdb_cluster_mutex(TALLOC_CTX *mem_ctx,
|
|
struct ctdb_context *ctdb,
|
|
const char *argstring,
|
|
int timeout,
|
|
cluster_mutex_handler_t handler,
|
|
void *private_data,
|
|
cluster_mutex_lost_handler_t lost_handler,
|
|
void *lost_data)
|
|
{
|
|
struct ctdb_cluster_mutex_handle *h;
|
|
char **args;
|
|
sigset_t sigset_term;
|
|
int ret;
|
|
|
|
h = talloc(mem_ctx, struct ctdb_cluster_mutex_handle);
|
|
if (h == NULL) {
|
|
DBG_ERR("out of memory\n");
|
|
return NULL;
|
|
}
|
|
|
|
h->start_time = timeval_current();
|
|
h->fd[0] = -1;
|
|
h->fd[1] = -1;
|
|
h->have_response = false;
|
|
|
|
ret = pipe(h->fd);
|
|
if (ret != 0) {
|
|
talloc_free(h);
|
|
DBG_ERR("Failed to open pipe\n");
|
|
return NULL;
|
|
}
|
|
set_close_on_exec(h->fd[0]);
|
|
|
|
/* Create arguments for lock helper */
|
|
if (!cluster_mutex_helper_args(h, argstring, &args)) {
|
|
close(h->fd[0]);
|
|
close(h->fd[1]);
|
|
talloc_free(h);
|
|
return NULL;
|
|
}
|
|
|
|
sigemptyset(&sigset_term);
|
|
sigaddset(&sigset_term, SIGTERM);
|
|
ret = sigprocmask(SIG_BLOCK, &sigset_term, NULL);
|
|
if (ret != 0) {
|
|
DBG_WARNING("Failed to block SIGTERM (%d)\n", errno);
|
|
}
|
|
|
|
h->child = ctdb_fork(ctdb);
|
|
if (h->child == (pid_t)-1) {
|
|
close(h->fd[0]);
|
|
close(h->fd[1]);
|
|
talloc_free(h);
|
|
ret = sigprocmask(SIG_UNBLOCK, &sigset_term, NULL);
|
|
if (ret != 0) {
|
|
DBG_WARNING("Failed to unblock SIGTERM (%d)\n", errno);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
if (h->child == 0) {
|
|
struct sigaction sa = {
|
|
.sa_handler = SIG_DFL,
|
|
};
|
|
|
|
ret = sigaction(SIGTERM, &sa, NULL);
|
|
if (ret != 0) {
|
|
DBG_WARNING("Failed to reset signal handler (%d)\n",
|
|
errno);
|
|
}
|
|
|
|
ret = sigprocmask(SIG_UNBLOCK, &sigset_term, NULL);
|
|
if (ret != 0) {
|
|
DBG_WARNING("Failed to unblock SIGTERM (%d)\n", errno);
|
|
}
|
|
|
|
/* Make stdout point to the pipe */
|
|
close(STDOUT_FILENO);
|
|
dup2(h->fd[1], STDOUT_FILENO);
|
|
close(h->fd[1]);
|
|
|
|
execv(args[0], args);
|
|
|
|
/* Only happens on error */
|
|
DBG_ERR("execv() failed\n");
|
|
_exit(1);
|
|
}
|
|
|
|
/* Parent */
|
|
|
|
ret = sigprocmask(SIG_UNBLOCK, &sigset_term, NULL);
|
|
if (ret != 0) {
|
|
DBG_WARNING("Failed to unblock SIGTERM (%d)\n", errno);
|
|
}
|
|
|
|
DBG_DEBUG("Created PIPE FD:%d\n", h->fd[0]);
|
|
set_close_on_exec(h->fd[0]);
|
|
|
|
close(h->fd[1]);
|
|
h->fd[1] = -1;
|
|
|
|
talloc_set_destructor(h, cluster_mutex_destructor);
|
|
|
|
if (timeout != 0) {
|
|
h->te = tevent_add_timer(ctdb->ev, h,
|
|
timeval_current_ofs(timeout, 0),
|
|
cluster_mutex_timeout, h);
|
|
} else {
|
|
h->te = NULL;
|
|
}
|
|
|
|
h->fde = tevent_add_fd(ctdb->ev, h, h->fd[0], TEVENT_FD_READ,
|
|
cluster_mutex_handler, (void *)h);
|
|
|
|
if (h->fde == NULL) {
|
|
talloc_free(h);
|
|
return NULL;
|
|
}
|
|
tevent_fd_set_auto_close(h->fde);
|
|
|
|
h->ctdb = ctdb;
|
|
h->handler = handler;
|
|
h->private_data = private_data;
|
|
h->lost_handler = lost_handler;
|
|
h->lost_data = lost_data;
|
|
|
|
return h;
|
|
}
|