/* 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 "system/wait.h" #include "system/dir.h" #include #include "lib/util/sys_rw.h" #include "lib/util/tevent_unix.h" #include "lib/util/util.h" #include "lib/util/smb_strtox.h" /* protocol.h is just needed for ctdb_sock_addr, which is used in system.h */ #include "protocol/protocol.h" #include "common/system.h" #include "common/tmon.h" static char progpath[PATH_MAX]; static char *progname = NULL; static int fcntl_lock_fd(int fd, bool block, off_t start) { static struct flock lock = { .l_type = F_WRLCK, .l_whence = SEEK_SET, .l_len = 1, .l_pid = 0, }; int cmd = block ? F_SETLKW : F_SETLK; lock.l_start = start; if (fcntl(fd, cmd, &lock) != 0) { return errno; } return 0; } static char fcntl_lock(const char *file, int *outfd) { int fd; int ret; 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'; } ret = fcntl_lock_fd(fd, false, 0); if (ret != 0) { close(fd); if (ret == EACCES || ret == 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(ret)); 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, "%s: tevent_wakeup_recv() failed\n", progname); } 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, int *perr) { if (tevent_req_is_unix_error(req, perr)) { return false; } return true; } /* * Perform I/O on lock in a loop - complete when file removed or replaced */ struct lock_io_check_state { struct tevent_context *ev; const char *lock_file; ino_t inode; unsigned long recheck_interval; }; static void lock_io_check_loop(struct tevent_req *subreq); static struct tevent_req *lock_io_check_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, const char *lock_file, ino_t inode, unsigned long recheck_interval) { struct tevent_req *req, *subreq; struct lock_io_check_state *state; req = tevent_req_create(mem_ctx, &state, struct lock_io_check_state); if (req == NULL) { return NULL; } state->ev = ev; state->lock_file = lock_file; state->inode = inode; state->recheck_interval = recheck_interval; subreq = tevent_wakeup_send( state, ev, tevent_timeval_current_ofs(state->recheck_interval, 0)); if (tevent_req_nomem(subreq, req)) { return tevent_req_post(req, ev); } tevent_req_set_callback(subreq, lock_io_check_loop, req); return req; } static void lock_io_check_loop(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct lock_io_check_state *state = tevent_req_data( req, struct lock_io_check_state); bool status; struct stat sb; int fd = -1; int ret; status = tevent_wakeup_recv(subreq); TALLOC_FREE(subreq); if (! status) { /* Ignore error */ fprintf(stderr, "%s: tevent_wakeup_recv() failed\n", progname); } fd = open(state->lock_file, O_RDWR); if (fd == -1) { fprintf(stderr, "%s: " "lock lost - lock file \"%s\" open failed (ret=%d)\n", progname, state->lock_file, errno); goto done; } ret = fstat(fd, &sb); if (ret != 0) { fprintf(stderr, "%s: " "lock lost - lock file \"%s\" check failed (ret=%d)\n", progname, state->lock_file, errno); goto done; } if (sb.st_ino != state->inode) { fprintf(stderr, "%s: lock lost - lock file \"%s\" inode changed\n", progname, state->lock_file); goto done; } /* * Attempt to lock a 2nd byte range. Using a blocking lock * encourages ping timeouts if the cluster filesystem is in a * bad state. It also makes testing easier. */ ret = fcntl_lock_fd(fd, true, 1); if (ret != 0) { fprintf(stderr, "%s: " "lock fail - lock file \"%s\" test lock error (%d)\n", progname, state->lock_file, ret); goto done; } /* Unlock occurs on close */ close(fd); subreq = tevent_wakeup_send( state, state->ev, tevent_timeval_current_ofs(state->recheck_interval, 0)); if (tevent_req_nomem(subreq, req)) { return; } tevent_req_set_callback(subreq, lock_io_check_loop, req); return; done: if (fd != -1) { close(fd); } tevent_req_done(req); } static bool lock_io_check_recv(struct tevent_req *req, int *perr) { if (tevent_req_is_unix_error(req, perr)) { return false; } return true; } struct lock_test_child_state { }; static void lock_test_child_ping_done(struct tevent_req *subreq); static void lock_test_child_io_check_done(struct tevent_req *subreq); static struct tevent_req *lock_test_child_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, const char *lock_file, int fd, ino_t inode, unsigned long recheck_interval, bool send_pings) { struct tevent_req *req, *subreq; struct lock_test_child_state *state; unsigned int interval = send_pings ? 1 : 0; req = tevent_req_create(mem_ctx, &state, struct lock_test_child_state); if (req == NULL) { return NULL; } subreq = tmon_ping_send(state, ev, fd, TMON_FD_BOTH, 0, interval); if (tevent_req_nomem(subreq, req)) { return tevent_req_post(req, ev); } tevent_req_set_callback(subreq, lock_test_child_ping_done, req); subreq = lock_io_check_send(state, ev, lock_file, inode, recheck_interval); if (tevent_req_nomem(subreq, req)) { return tevent_req_post(req, ev); } tevent_req_set_callback(subreq, lock_test_child_io_check_done, req); return req; } static void lock_test_child_ping_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); bool status; int err; status = tmon_ping_recv(subreq, &err); TALLOC_FREE(subreq); if (!status) { tevent_req_error(req, err); return; } tevent_req_done(req); } static void lock_test_child_io_check_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); bool status; int err; status = lock_io_check_recv(subreq, &err); TALLOC_FREE(subreq); if (!status) { tevent_req_error(req, err); return; } tevent_req_done(req); } static bool lock_test_child_recv(struct tevent_req *req, int *perr) { if (tevent_req_is_unix_error(req, perr)) { /* Parent exit is expected */ if (*perr == EPIPE) { return true; } return false; } return true; } static void lock_test_child(const char *lock_file, int lock_fd, int pipe_fd, unsigned long recheck_interval, bool send_pings) { struct tevent_context *ev; struct tevent_req *req; struct stat sb; ino_t inode; bool status; int ret; ret = fstat(lock_fd, &sb); if (ret != 0) { fprintf(stderr, "%s: lock lost - " "lock file \"%s\" stat failed (ret=%d)\n", progname, lock_file, errno); _exit(1); } inode = sb.st_ino; close(lock_fd); ev = tevent_context_init(NULL); if (ev == NULL) { fprintf(stderr, "%s: tevent_context_init() failed\n", progname); _exit(1); } req = lock_test_child_send(ev, ev, lock_file, pipe_fd, inode, recheck_interval, send_pings); if (req == NULL) { fprintf(stderr, "%s: lock_test_child_send() failed\n", progname); _exit(1); } tevent_req_poll(req, ev); status = lock_test_child_recv(req, &ret); if (! status) { fprintf(stderr, "%s: lock_test_child_recv() failed (%d)\n", progname, ret); _exit(1); } _exit(0); } struct lock_test_state { int *lock_fdp; int pipe_fd; pid_t child_pid; }; static void lock_test_ping_done(struct tevent_req *subreq); static struct tevent_req *lock_test_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, const char *lock_file, int *fdp, unsigned long recheck_interval, unsigned long ping_timeout) { struct tevent_req *req, *subreq; struct lock_test_state *state; pid_t pid; int sv[2]; int ret; req = tevent_req_create(mem_ctx, &state, struct lock_test_state); if (req == NULL) { return NULL; } ret = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); if (ret != 0) { fprintf(stderr, "%s: socketpair() failed (errno=%d)\n", progname, errno); tevent_req_error(req, errno); return tevent_req_post(req, ev); } pid = fork(); if (pid == -1) { int err = errno; fprintf(stderr, "%s: fork() failed (errno=%d)\n", progname, err); close(sv[0]); close(sv[1]); tevent_req_error(req, err); return tevent_req_post(req, ev); } if (pid == 0) { /* Child */ close(sv[0]); TALLOC_FREE(ev); lock_test_child(lock_file, *fdp, sv[1], recheck_interval, ping_timeout != 0); /* Above does not return */ } /* Parent */ close(sv[1]); state->lock_fdp = fdp; state->pipe_fd = sv[0]; state->child_pid = pid; subreq = tmon_ping_send(state, ev, sv[0], TMON_FD_BOTH, ping_timeout, 0); if (tevent_req_nomem(subreq, req)) { close(sv[0]); return tevent_req_post(req, ev); } tevent_req_set_callback(subreq, lock_test_ping_done, req); return req; } static void lock_test_ping_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct lock_test_state *state = tevent_req_data( req, struct lock_test_state); int wstatus; bool status; int err; status = tmon_ping_recv(subreq, &err); TALLOC_FREE(subreq); if (! status) { switch (err) { case EPIPE: /* Child exit, child already printed message */ break; case ETIMEDOUT: fprintf(stderr, "%s: ping timeout from lock test child\n", progname); break; default: fprintf(stderr, "%s: tmon_ping_recv() failed (%d)\n", progname, err); } /* Ignore error */ } /* * Lock checking child is gone or not sending pings. Release * the lock, close this end of pipe, send SIGKILL to the child * process and wait for the child to exit. */ close(*state->lock_fdp); *state->lock_fdp = -1; close(state->pipe_fd); kill(state->child_pid, SIGKILL); waitpid(state->child_pid, &wstatus, 0); tevent_req_done(req); } static bool lock_test_recv(struct tevent_req *req, int *perr) { if (tevent_req_is_unix_error(req, perr)) { return false; } return true; } /* * Wait for a reason to exit, indicating that parent has exited or I/O * on lock failed */ struct wait_for_exit_state { }; static void wait_for_exit_parent_done(struct tevent_req *subreq); static void wait_for_exit_lock_test_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 *fdp, unsigned long recheck_interval, unsigned long ping_timeout) { 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_interval > 0) { subreq = lock_test_send(state, ev, lock_file, fdp, recheck_interval, ping_timeout); if (tevent_req_nomem(subreq, req)) { return tevent_req_post(req, ev); } tevent_req_set_callback(subreq, wait_for_exit_lock_test_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; int err; status = wait_for_parent_recv(subreq, &err); TALLOC_FREE(subreq); if (! status) { /* Ignore error */ fprintf(stderr, "%s: " "wait_for_parent_recv() failed (%d)\n", progname, err); } tevent_req_done(req); } static void wait_for_exit_lock_test_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); bool status; int err; status = lock_test_recv(subreq, &err); TALLOC_FREE(subreq); if (! status) { fprintf(stderr, "%s: " "lock_test_recv() failed (%d)\n", progname, err); /* Ignore error, fall through to done */ } tevent_req_done(req); } static bool wait_for_exit_recv(struct tevent_req *req, int *perr) { if (tevent_req_is_unix_error(req, perr)) { return false; } return true; } static void usage(void) { fprintf(stderr, "Usage: %s [recheck_interval [ping_timeout]]\n", progname); } int main(int argc, char *argv[]) { struct tevent_context *ev; char result; int ppid; const char *file = NULL; unsigned long recheck_interval; unsigned long ping_timeout; int ret; int fd = -1; struct tevent_req *req; bool status; strlcpy(progpath, argv[0], sizeof(progpath)); progname = basename(progpath); if (argc < 2 || argc > 4) { 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_interval = 5; ping_timeout = 0; if (argc >= 3) { recheck_interval = smb_strtoul(argv[2], NULL, 10, &ret, SMB_STR_STANDARD); if (ret != 0) { usage(); exit(1); } } if (argc >= 4) { ping_timeout = smb_strtoul(argv[3], 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_interval, ping_timeout); 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, &ret); if (! status) { fprintf(stderr, "%s: wait_for_exit_recv() failed (%d)\n", progname, ret); } if (fd != -1) { close(fd); } return 0; }