/*
CTDB mutex fcntl lock file helper
Copyright (C) Martin Schwenke 2015
wait_for_parent() code from ctdb_lock_helper.c:
Copyright (C) Amitay Isaacs 2013
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/network.h"
#include
#include "lib/util/sys_rw.h"
#include "lib/util/tevent_unix.h"
#include "lib/util/util.h"
/* protocol.h is just needed for ctdb_sock_addr, which is used in system.h */
#include "protocol/protocol.h"
#include "common/system.h"
static char *progname = NULL;
static char fcntl_lock(const char *file, int *outfd)
{
int fd;
struct flock lock;
fd = open(file, O_RDWR|O_CREAT, 0600);
if (fd == -1) {
fprintf(stderr, "%s: Unable to open %s - (%s)\n",
progname, file, strerror(errno));
return '3';
}
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 1;
lock.l_pid = 0;
if (fcntl(fd, F_SETLK, &lock) != 0) {
int saved_errno = errno;
close(fd);
if (saved_errno == EACCES ||
saved_errno == EAGAIN) {
/* Lock contention, fail silently */
return '1';
}
/* Log an error for any other failure */
fprintf(stderr,
"%s: Failed to get lock on '%s' - (%s)\n",
progname, file, strerror(saved_errno));
return '3';
}
*outfd = fd;
return '0';
}
/*
* Wait and see if the parent exits
*/
struct wait_for_parent_state {
struct tevent_context *ev;
pid_t ppid;
};
static void wait_for_parent_check(struct tevent_req *subreq);
static struct tevent_req *wait_for_parent_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
pid_t ppid)
{
struct tevent_req *req, *subreq;
struct wait_for_parent_state *state;
req = tevent_req_create(mem_ctx, &state, struct wait_for_parent_state);
if (req == NULL) {
return NULL;
}
state->ev = ev;
state->ppid = ppid;
if (ppid == 1) {
fprintf(stderr, "parent == 1\n");
tevent_req_done(req);
return tevent_req_post(req, ev);
}
subreq = tevent_wakeup_send(state, ev,
tevent_timeval_current_ofs(5,0));
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq, wait_for_parent_check, req);
return req;
}
static void wait_for_parent_check(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct wait_for_parent_state *state = tevent_req_data(
req, struct wait_for_parent_state);
bool status;
status = tevent_wakeup_recv(subreq);
TALLOC_FREE(subreq);
if (! status) {
/* Ignore error */
fprintf(stderr,
"ctdb_mutex_fcntl_helper: "
"tevent_wakeup_recv() failed\n");
}
if (kill(state->ppid, 0) == -1 && errno == ESRCH) {
fprintf(stderr, "parent gone\n");
tevent_req_done(req);
return;
}
subreq = tevent_wakeup_send(state, state->ev,
tevent_timeval_current_ofs(5,0));
if (tevent_req_nomem(subreq, req)) {
return;
}
tevent_req_set_callback(subreq, wait_for_parent_check, req);
}
static bool wait_for_parent_recv(struct tevent_req *req)
{
if (tevent_req_is_unix_error(req, NULL)) {
return false;
}
return true;
}
/*
* Wait and check for lost lock - file removed or replaced
*/
struct wait_for_lost_state {
struct tevent_context *ev;
const char *lock_file;
ino_t inode;
unsigned long recheck_time;
};
static void wait_for_lost_check(struct tevent_req *subreq);
static struct tevent_req *wait_for_lost_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
const char *lock_file,
int fd,
unsigned long recheck_time)
{
struct tevent_req *req, *subreq;
struct wait_for_lost_state *state;
struct stat sb;
int ret;
req = tevent_req_create(mem_ctx, &state, struct wait_for_lost_state);
if (req == NULL) {
return NULL;
}
state->ev = ev;
state->lock_file = lock_file;
state->recheck_time = recheck_time;
ret = fstat(fd, &sb);
if (ret != 0) {
fprintf(stderr,
"ctdb_mutex_fcntl_helper: "
"lock lost - lock file \"%s\" check failed (ret=%d)\n",
state->lock_file,
errno);
tevent_req_done(req);
return tevent_req_post(req, ev);
}
state->inode = sb.st_ino;
subreq = tevent_wakeup_send(
state,
ev,
tevent_timeval_current_ofs(state->recheck_time, 0));
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq, wait_for_lost_check, req);
return req;
}
static void wait_for_lost_check(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct wait_for_lost_state *state = tevent_req_data(
req, struct wait_for_lost_state);
bool status;
struct stat sb;
int ret;
status = tevent_wakeup_recv(subreq);
TALLOC_FREE(subreq);
if (! status) {
/* Ignore error */
fprintf(stderr,
"ctdb_mutex_fcntl_helper: "
"tevent_wakeup_recv() failed\n");
}
ret = stat(state->lock_file, &sb);
if (ret != 0) {
fprintf(stderr,
"ctdb_mutex_fcntl_helper: "
"lock lost - lock file \"%s\" check failed (ret=%d)\n",
state->lock_file,
errno);
tevent_req_done(req);
return;
}
if (sb.st_ino != state->inode) {
fprintf(stderr,
"ctdb_mutex_fcntl_helper: "
"lock lost - lock file \"%s\" inode changed\n",
state->lock_file);
tevent_req_done(req);
return;
}
subreq = tevent_wakeup_send(
state,
state->ev,
tevent_timeval_current_ofs(state->recheck_time, 0));
if (tevent_req_nomem(subreq, req)) {
return;
}
tevent_req_set_callback(subreq, wait_for_lost_check, req);
}
static bool wait_for_lost_recv(struct tevent_req *req)
{
if (tevent_req_is_unix_error(req, NULL)) {
return false;
}
return true;
}
/*
* Wait for a reason to exit, indicating that the lock is lost
*/
struct wait_for_exit_state {
};
static void wait_for_exit_parent_done(struct tevent_req *subreq);
static void wait_for_exit_lost_done(struct tevent_req *subreq);
static struct tevent_req *wait_for_exit_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
pid_t ppid,
const char *lock_file,
int fd,
unsigned long recheck_time)
{
struct tevent_req *req, *subreq;
struct wait_for_exit_state *state;
req = tevent_req_create(mem_ctx, &state, struct wait_for_exit_state);
if (req == NULL) {
return NULL;
}
subreq = wait_for_parent_send(state, ev, ppid);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq, wait_for_exit_parent_done, req);
if (recheck_time > 0) {
subreq = wait_for_lost_send(state,
ev,
lock_file,
fd,
recheck_time);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq, wait_for_exit_lost_done, req);
}
return req;
}
static void wait_for_exit_parent_done(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
bool status;
status = wait_for_parent_recv(subreq);
TALLOC_FREE(subreq);
if (! status) {
/* Ignore error */
fprintf(stderr,
"ctdb_mutex_fcntl_helper: "
"wait_for_parent_recv() failed\n");
}
tevent_req_done(req);
}
static void wait_for_exit_lost_done(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
bool status;
status = wait_for_lost_recv(subreq);
TALLOC_FREE(subreq);
if (! status) {
/* Ignore error */
fprintf(stderr,
"ctdb_mutex_fcntl_helper: "
"wait_for_lost_recv() failed\n");
}
tevent_req_done(req);
}
static bool wait_for_exit_recv(struct tevent_req *req)
{
if (tevent_req_is_unix_error(req, NULL)) {
return false;
}
return true;
}
static void usage(void)
{
fprintf(stderr, "Usage: %s [recheck_time]\n", progname);
}
int main(int argc, char *argv[])
{
struct tevent_context *ev;
char result;
int ppid;
const char *file = NULL;
unsigned long recheck_time;
int ret;
int fd = -1;
struct tevent_req *req;
bool status;
progname = argv[0];
if (argc < 2 || argc > 3) {
usage();
exit(1);
}
ev = tevent_context_init(NULL);
if (ev == NULL) {
fprintf(stderr, "locking: tevent_context_init() failed\n");
exit(1);
}
ppid = getppid();
file = argv[1];
recheck_time = 5;
if (argc == 3) {
recheck_time = smb_strtoul(argv[2],
NULL,
10,
&ret,
SMB_STR_STANDARD);
if (ret != 0) {
usage();
exit(1);
}
}
result = fcntl_lock(file, &fd);
sys_write(STDOUT_FILENO, &result, 1);
if (result != '0') {
return 0;
}
req = wait_for_exit_send(ev, ev, ppid, file, fd, recheck_time);
if (req == NULL) {
fprintf(stderr,
"%s: wait_for_exit_send() failed\n",
progname);
exit(1);
}
tevent_req_poll(req, ev);
status = wait_for_exit_recv(req);
if (! status) {
fprintf(stderr,
"%s: wait_for_exit_recv() failed\n",
progname);
}
if (fd != -1) {
close(fd);
}
return 0;
}