1
0
mirror of https://github.com/samba-team/samba.git synced 2024-12-25 23:21:54 +03:00
samba-mirror/ctdb/server/ctdb_cluster_mutex.c
Martin Schwenke e789d0da57 ctdb-cluster-mutex: Block signals around fork
If SIGTERM is received and the tevent signal handler setup in the
recovery daemon is still enabled then the signal is handled and a
corresponding event is queued.  The child never runs an event loop so
the signal is effectively ignored.

Resetting the SIGTERM handler isn't enough.  A signal can arrive
before that.

Block SIGTERM before forking and then immediately unblock it in the
parent.

In the child, unblock SIGTERM after the signal handler is reset.  An
explicit unblock is needed because according to sigprocmask(2) "the
signal mask is preserved across execve(2)".

BUG: https://bugzilla.samba.org/show_bug.cgi?id=13617

Signed-off-by: Martin Schwenke <martin@meltin.net>
Reviewed-by: Amitay Isaacs <amitay@gmail.com>
2018-09-17 22:58:20 +02:00

316 lines
7.5 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 <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 "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;
sigset_t sigset_term;
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;
}
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 */
DEBUG(DEBUG_ERR, (__location__ "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);
}
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;
}