/*
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 .
*/
#include
#include "replace.h"
#include "system/network.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 "common/common.h"
#include "common/logging.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(TALLOC_CTX *mem_ctx,
const char *argstring, char ***argv)
{
int nargs, i, ret, n;
bool is_command = false;
char **args = NULL;
char *strv = NULL;
char *t = NULL;
if (argstring != NULL && argstring[0] == '!') {
/* This is actually a full command */
is_command = true;
t = discard_const(&argstring[1]);
} else {
is_command = false;
t = discard_const(argstring);
}
ret = strv_split(mem_ctx, &strv, t, " \t");
if (ret != 0) {
DEBUG(DEBUG_ERR,
("Unable to parse mutex helper string \"%s\" (%s)\n",
argstring, strerror(ret)));
return false;
}
n = strv_count(strv);
args = talloc_array(mem_ctx, char *, n + (is_command ? 1 : 2));
if (args == NULL) {
DEBUG(DEBUG_ERR,(__location__ " out of memory\n"));
return false;
}
nargs = 0;
if (! is_command) {
if (!ctdb_set_helper("cluster mutex helper",
cluster_mutex_helper,
sizeof(cluster_mutex_helper),
"CTDB_CLUSTER_MUTEX_HELPER",
CTDB_HELPER_BINDIR,
"ctdb_mutex_fcntl_helper")) {
DEBUG(DEBUG_ERR,("ctdb exiting with error: %s\n",
__location__
" Unable to set cluster mutex helper\n"));
exit(1);
}
args[nargs++] = cluster_mutex_helper;
}
t = NULL;
for (i = 0; i < n; i++) {
/* Don't copy, just keep cmd_args around */
t = strv_next(strv, t);
args[nargs++] = t;
}
/* Make sure last argument is NULL */
args[nargs] = NULL;
*argv = args;
return true;
}
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;
int ret;
h = talloc(mem_ctx, struct ctdb_cluster_mutex_handle);
if (h == NULL) {
DEBUG(DEBUG_ERR, (__location__ " 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);
DEBUG(DEBUG_ERR, (__location__ " 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;
}
h->child = ctdb_fork(ctdb);
if (h->child == (pid_t)-1) {
close(h->fd[0]);
close(h->fd[1]);
talloc_free(h);
return NULL;
}
if (h->child == 0) {
/* 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 */
DEBUG(DEBUG_ERR, (__location__ "execv() failed\n"));
_exit(1);
}
/* Parent */
DEBUG(DEBUG_DEBUG, (__location__ " 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;
}