diff options
Diffstat (limited to 'daemons/lvmpolld/lvmpolld-core.c')
-rw-r--r-- | daemons/lvmpolld/lvmpolld-core.c | 989 |
1 files changed, 989 insertions, 0 deletions
diff --git a/daemons/lvmpolld/lvmpolld-core.c b/daemons/lvmpolld/lvmpolld-core.c new file mode 100644 index 000000000..7d2a98b23 --- /dev/null +++ b/daemons/lvmpolld/lvmpolld-core.c @@ -0,0 +1,989 @@ +/* + * Copyright (C) 2014-2015 Red Hat, Inc. + * + * This file is part of LVM2. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License v.2.1. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "lvmpolld-common.h" + +#include "lvm-version.h" +#include "daemon-server.h" +#include "daemon-log.h" + +#include <getopt.h> +#include <poll.h> +#include <wait.h> + +#define LVMPOLLD_SOCKET DEFAULT_RUN_DIR "/lvmpolld.socket" + +#define PD_LOG_PREFIX "LVMPOLLD" +#define LVM2_LOG_PREFIX "\tLVPOLL" + +/* predefined reason for response = "failed" case */ +#define REASON_REQ_NOT_IMPLEMENTED "request not implemented" +#define REASON_MISSING_LVID "request requires lvid set" +#define REASON_MISSING_LVNAME "request requires lvname set" +#define REASON_MISSING_VGNAME "request requires vgname set" +#define REASON_POLLING_FAILED "polling of lvm command failed" +#define REASON_ILLEGAL_ABORT_REQUEST "abort only supported with PVMOVE polling operation" +#define REASON_DIFFERENT_OPERATION_IN_PROGRESS "Different operation on LV already in progress" +#define REASON_INVALID_INTERVAL "request requires interval set" +#define REASON_ENOMEM "not enough memory" + +struct lvmpolld_state { + daemon_idle *idle; + log_state *log; + const char *log_config; + const char *lvm_binary; + + struct lvmpolld_store *id_to_pdlv_abort; + struct lvmpolld_store *id_to_pdlv_poll; +}; + +static pthread_key_t key; + +static const char *_strerror_r(int errnum, struct lvmpolld_thread_data *data) +{ +#ifdef _GNU_SOURCE + return strerror_r(errnum, data->buf, sizeof(data->buf)); /* never returns NULL */ +#elif (_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) + return strerror_r(errnum, data->buf, sizeof(data->buf)) ? "" : data->buf; +#else +# warning "Can't decide proper strerror_r implementation. lvmpolld will not issue specific system error messages" + return ""; +#endif +} + +static void _usage(const char *prog, FILE *file) +{ + fprintf(file, "Usage:\n" + "%s [-V] [-h] [-f] [-l {all|wire|debug}] [-s path] [-B path] [-p path] [-t secs]\n" + "%s --dump [-s path]\n" + " -V|--version Show version info\n" + " -h|--help Show this help information\n" + " -f|--foreground Don't fork, run in the foreground\n" + " --dump Dump full lvmpolld state\n" + " -l|--log Logging message level (-l {all|wire|debug})\n" + " -p|--pidfile Set path to the pidfile\n" + " -s|--socket Set path to the communication socket\n" + " -B|--binary Path to lvm2 binary\n" + " -t|--timeout Time to wait in seconds before shutdown on idle (missing or 0 = inifinite)\n\n", prog, prog); +} + +static int _init(struct daemon_state *s) +{ + struct lvmpolld_state *ls = s->private; + ls->log = s->log; + + if (!daemon_log_parse(ls->log, DAEMON_LOG_OUTLET_STDERR, ls->log_config, 1)) + return 0; + + if (pthread_key_create(&key, lvmpolld_thread_data_destroy)) { + FATAL(ls, "%s: %s", PD_LOG_PREFIX, "Failed to create pthread key"); + return 0; + } + + ls->id_to_pdlv_poll = pdst_init("polling"); + ls->id_to_pdlv_abort = pdst_init("abort"); + + if (!ls->id_to_pdlv_poll || !ls->id_to_pdlv_abort) { + FATAL(ls, "%s: %s", PD_LOG_PREFIX, "Failed to allocate internal data structures"); + return 0; + } + + ls->lvm_binary = ls->lvm_binary ?: LVM_PATH; + + if (access(ls->lvm_binary, X_OK)) { + FATAL(ls, "%s: %s %s", PD_LOG_PREFIX, "Execute access rights denied on", ls->lvm_binary); + return 0; + } + + if (ls->idle) + ls->idle->is_idle = 1; + + return 1; +} + +static void _lvmpolld_stores_lock(struct lvmpolld_state *ls) +{ + pdst_lock(ls->id_to_pdlv_poll); + pdst_lock(ls->id_to_pdlv_abort); +} + +static void _lvmpolld_stores_unlock(struct lvmpolld_state *ls) +{ + pdst_unlock(ls->id_to_pdlv_abort); + pdst_unlock(ls->id_to_pdlv_poll); +} + +static void _lvmpolld_global_lock(struct lvmpolld_state *ls) +{ + _lvmpolld_stores_lock(ls); + + pdst_locked_lock_all_pdlvs(ls->id_to_pdlv_poll); + pdst_locked_lock_all_pdlvs(ls->id_to_pdlv_abort); +} + +static void _lvmpolld_global_unlock(struct lvmpolld_state *ls) +{ + pdst_locked_unlock_all_pdlvs(ls->id_to_pdlv_abort); + pdst_locked_unlock_all_pdlvs(ls->id_to_pdlv_poll); + + _lvmpolld_stores_unlock(ls); +} + +static int _fini(struct daemon_state *s) +{ + int done; + const struct timespec t = { .tv_nsec = 250000000 }; /* .25 sec */ + struct lvmpolld_state *ls = s->private; + + DEBUGLOG(s, "fini"); + + DEBUGLOG(s, "sending cancel requests"); + + _lvmpolld_global_lock(ls); + pdst_locked_send_cancel(ls->id_to_pdlv_poll); + pdst_locked_send_cancel(ls->id_to_pdlv_abort); + _lvmpolld_global_unlock(ls); + + DEBUGLOG(s, "waiting for background threads to finish"); + + while(1) { + _lvmpolld_stores_lock(ls); + done = !pdst_locked_get_active_count(ls->id_to_pdlv_poll) && + !pdst_locked_get_active_count(ls->id_to_pdlv_abort); + _lvmpolld_stores_unlock(ls); + if (done) + break; + nanosleep(&t, NULL); + } + + DEBUGLOG(s, "destroying internal data structures"); + + _lvmpolld_stores_lock(ls); + pdst_locked_destroy_all_pdlvs(ls->id_to_pdlv_poll); + pdst_locked_destroy_all_pdlvs(ls->id_to_pdlv_abort); + _lvmpolld_stores_unlock(ls); + + pdst_destroy(ls->id_to_pdlv_poll); + pdst_destroy(ls->id_to_pdlv_abort); + + pthread_key_delete(key); + + return 1; +} + +static response reply(const char *res, const char *reason) +{ + return daemon_reply_simple(res, "reason = %s", reason, NULL); +} + +static int read_single_line(struct lvmpolld_thread_data *data, int err) +{ + ssize_t r = getline(&data->line, &data->line_size, err ? data->ferr : data->fout); + + if (r > 0 && *(data->line + r - 1) == '\n') + *(data->line + r - 1) = '\0'; + + return (r > 0); +} + +static void update_idle_state(struct lvmpolld_state *ls) +{ + if (!ls->idle) + return; + + _lvmpolld_stores_lock(ls); + + ls->idle->is_idle = !pdst_locked_get_active_count(ls->id_to_pdlv_poll) && + !pdst_locked_get_active_count(ls->id_to_pdlv_abort); + + _lvmpolld_stores_unlock(ls); + + DEBUGLOG(ls, "%s: %s %s%s", PD_LOG_PREFIX, "daemon is", ls->idle->is_idle ? "" : "not ", "idle"); +} + +/* make this configurable */ +#define MAX_TIMEOUT 2 + +static int poll_for_output(struct lvmpolld_lv *pdlv, struct lvmpolld_thread_data *data) +{ + int ch_stat, r, err = 1, fds_count = 2, timeout = 0; + pid_t pid; + struct lvmpolld_cmd_stat cmd_state = { .retcode = -1, .signal = 0 }; + struct pollfd fds[] = { { .fd = data->outpipe[0], .events = POLLIN }, + { .fd = data->errpipe[0], .events = POLLIN } }; + + if (!(data->fout = fdopen(data->outpipe[0], "r")) || !(data->ferr = fdopen(data->errpipe[0], "r"))) { + ERROR(pdlv->ls, "%s: %s: (%d) %s", PD_LOG_PREFIX, "failed to open file stream", + errno, _strerror_r(errno, data)); + goto out; + } + + while (1) { + do { + r = poll(fds, 2, pdlv_get_timeout(pdlv) * 1000); + } while (r < 0 && errno == EINTR); + + DEBUGLOG(pdlv->ls, "%s: %s %d", PD_LOG_PREFIX, "poll() returned", r); + if (r < 0) { + ERROR(pdlv->ls, "%s: %s (PID %d) failed: (%d) %s", + PD_LOG_PREFIX, "poll() for LVM2 cmd", pdlv->cmd_pid, + errno, _strerror_r(errno, data)); + goto out; + } else if (!r) { + timeout++; + + WARN(pdlv->ls, "%s: %s (PID %d) %s", PD_LOG_PREFIX, + "polling for output of the lvm cmd", pdlv->cmd_pid, + "has timed out"); + + if (timeout > MAX_TIMEOUT) { + ERROR(pdlv->ls, "%s: %s (PID %d) (no output for %d seconds)", + PD_LOG_PREFIX, + "LVM2 cmd is unresponsive too long", + pdlv->cmd_pid, + timeout * pdlv_get_timeout(pdlv)); + goto out; + } + + continue; /* while(1) */ + } + + timeout = 0; + + /* handle the command's STDOUT */ + if (fds[0].revents & POLLIN) { + DEBUGLOG(pdlv->ls, "%s: %s", PD_LOG_PREFIX, "caught input data in STDOUT"); + + assert(read_single_line(data, 0)); /* may block indef. anyway */ + INFO(pdlv->ls, "%s: PID %d: %s: '%s'", LVM2_LOG_PREFIX, + pdlv->cmd_pid, "STDOUT", data->line); + } else if (fds[0].revents) { + if (fds[0].revents & POLLHUP) + DEBUGLOG(pdlv->ls, "%s: %s", PD_LOG_PREFIX, "caught POLLHUP"); + else + WARN(pdlv->ls, "%s: %s", PD_LOG_PREFIX, "poll for command's STDOUT failed"); + + fds[0].fd = -1; + fds_count--; + } + + /* handle the command's STDERR */ + if (fds[1].revents & POLLIN) { + DEBUGLOG(pdlv->ls, "%s: %s", PD_LOG_PREFIX, + "caught input data in STDERR"); + + assert(read_single_line(data, 1)); /* may block indef. anyway */ + INFO(pdlv->ls, "%s: PID %d: %s: '%s'", LVM2_LOG_PREFIX, + pdlv->cmd_pid, "STDERR", data->line); + } else if (fds[1].revents) { + if (fds[1].revents & POLLHUP) + DEBUGLOG(pdlv->ls, "%s: %s", PD_LOG_PREFIX, "caught err POLLHUP"); + else + WARN(pdlv->ls, "%s: %s", PD_LOG_PREFIX, "poll for command's STDOUT failed"); + + fds[1].fd = -1; + fds_count--; + } + + do { + /* + * fds_count == 0 means polling reached EOF + * or received error on both descriptors. + * In such case, just wait for command to finish + */ + pid = waitpid(pdlv->cmd_pid, &ch_stat, fds_count ? WNOHANG : 0); + } while (pid < 0 && errno == EINTR); + + if (pid) { + if (pid < 0) { + ERROR(pdlv->ls, "%s: %s (PID %d) failed: (%d) %s", + PD_LOG_PREFIX, "waitpid() for lvm2 cmd", + pdlv->cmd_pid, errno, + _strerror_r(errno, data)); + goto out; + } + DEBUGLOG(pdlv->ls, "%s: %s", PD_LOG_PREFIX, "child exited"); + break; + } + } /* while(1) */ + + DEBUGLOG(pdlv->ls, "%s: %s", PD_LOG_PREFIX, "about to collect remaining lines"); + if (fds[0].fd >= 0) + while (read_single_line(data, 0)) { + assert(r > 0); + INFO(pdlv->ls, "%s: PID %d: %s: %s", LVM2_LOG_PREFIX, pdlv->cmd_pid, "STDOUT", data->line); + } + if (fds[1].fd >= 0) + while (read_single_line(data, 1)) { + assert(r > 0); + INFO(pdlv->ls, "%s: PID %d: %s: %s", LVM2_LOG_PREFIX, pdlv->cmd_pid, "STDERR", data->line); + } + + if (WIFEXITED(ch_stat)) { + INFO(pdlv->ls, "%s: %s (PID %d) %s (%d)", PD_LOG_PREFIX, + "lvm2 cmd", pdlv->cmd_pid, "exited with", WEXITSTATUS(ch_stat)); + cmd_state.retcode = WEXITSTATUS(ch_stat); + } else if (WIFSIGNALED(ch_stat)) { + WARN(pdlv->ls, "%s: %s (PID %d) %s (%d)", PD_LOG_PREFIX, + "lvm2 cmd", pdlv->cmd_pid, "got terminated by signal", + WTERMSIG(ch_stat)); + cmd_state.signal = WTERMSIG(ch_stat); + } + + err = 0; +out: + if (!err) + pdlv_set_cmd_state(pdlv, &cmd_state); + + return err; +} + +static void debug_print(struct lvmpolld_state *ls, const char * const* ptr) +{ + const char * const* tmp = ptr; + + if (!tmp) + return; + + while (*tmp) { + DEBUGLOG(ls, "%s: %s", PD_LOG_PREFIX, *tmp); + tmp++; + } +} + +static void *fork_and_poll(void *args) +{ + int outfd, errfd, state; + struct lvmpolld_thread_data *data; + pid_t r; + + int error = 1; + struct lvmpolld_lv *pdlv = (struct lvmpolld_lv *) args; + struct lvmpolld_state *ls = pdlv->ls; + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &state); + data = lvmpolld_thread_data_constructor(pdlv); + pthread_setspecific(key, data); + pthread_setcancelstate(state, &state); + + if (!data) { + ERROR(ls, "%s: %s", PD_LOG_PREFIX, "Failed to initialize per-thread data"); + goto err; + } + + DEBUGLOG(ls, "%s: %s", PD_LOG_PREFIX, "cmd line arguments:"); + debug_print(ls, pdlv->cmdargv); + DEBUGLOG(ls, "%s: %s", PD_LOG_PREFIX, "---end---"); + + DEBUGLOG(ls, "%s: %s", PD_LOG_PREFIX, "cmd environment variables:"); + debug_print(ls, pdlv->cmdenvp); + DEBUGLOG(ls, "%s: %s", PD_LOG_PREFIX, "---end---"); + + outfd = data->outpipe[1]; + errfd = data->errpipe[1]; + + r = fork(); + if (!r) { + /* child */ + /* !!! Do not touch any posix thread primitives !!! */ + + if ((dup2(outfd, STDOUT_FILENO ) != STDOUT_FILENO) || + (dup2(errfd, STDERR_FILENO ) != STDERR_FILENO)) + _exit(LVMPD_RET_DUP_FAILED); + + execve(*(pdlv->cmdargv), (char *const *)pdlv->cmdargv, (char *const *)pdlv->cmdenvp); + + _exit(LVMPD_RET_EXC_FAILED); + } else { + /* parent */ + if (r == -1) { + ERROR(ls, "%s: %s: (%d) %s", PD_LOG_PREFIX, "fork failed", + errno, _strerror_r(errno, data)); + goto err; + } + + INFO(ls, "%s: LVM2 cmd \"%s\" (PID: %d)", PD_LOG_PREFIX, *(pdlv->cmdargv), r); + + pdlv->cmd_pid = r; + + /* failure to close write end of any pipe will result in broken polling */ + if (close(data->outpipe[1])) { + ERROR(ls, "%s: %s: (%d) %s", PD_LOG_PREFIX, "failed to close write end of pipe", + errno, _strerror_r(errno, data)); + goto err; + } + data->outpipe[1] = -1; + + if (close(data->errpipe[1])) { + ERROR(ls, "%s: %s: (%d) %s", PD_LOG_PREFIX, "failed to close write end of err pipe", + errno, _strerror_r(errno, data)); + goto err; + } + data->errpipe[1] = -1; + + error = poll_for_output(pdlv, data); + DEBUGLOG(ls, "%s: %s", PD_LOG_PREFIX, "polling for lvpoll output has finished"); + } + +err: + r = 0; + + pdst_lock(pdlv->pdst); + + if (error) { + /* last reader is responsible for pdlv cleanup */ + r = pdlv->cmd_pid; + pdlv_set_error(pdlv, 1); + } + + pdlv_set_polling_finished(pdlv, 1); + if (data) + data->pdlv = NULL; + + pdst_locked_dec(pdlv->pdst); + + pdst_unlock(pdlv->pdst); + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &state); + lvmpolld_thread_data_destroy(data); + pthread_setspecific(key, NULL); + pthread_setcancelstate(state, &state); + + update_idle_state(ls); + + /* + * This is unfortunate case where we + * know nothing about state of lvm cmd and + * (eventually) ongoing progress. + * + * harvest zombies + */ + if (r) + while(waitpid(r, NULL, 0) < 0 && errno == EINTR); + + return NULL; +} + +static response progress_info(client_handle h, struct lvmpolld_state *ls, request req) +{ + char *id; + struct lvmpolld_lv *pdlv; + struct lvmpolld_store *pdst; + struct lvmpolld_lv_state st; + response r; + const char *lvid = daemon_request_str(req, LVMPD_PARM_LVID, NULL); + const char *sysdir = daemon_request_str(req, LVMPD_PARM_SYSDIR, NULL); + unsigned abort_polling = daemon_request_int(req, LVMPD_PARM_ABORT, 0); + + if (!lvid) + return reply(LVMPD_RESP_FAILED, REASON_MISSING_LVID); + + id = construct_id(sysdir, lvid); + if (!id) { + ERROR(ls, "%s: %s", PD_LOG_PREFIX, "progress_info request failed to construct ID."); + return reply(LVMPD_RESP_FAILED, REASON_ENOMEM); + } + + DEBUGLOG(ls, "%s: %s: %s", PD_LOG_PREFIX, "ID", id); + + pdst = abort_polling ? ls->id_to_pdlv_abort : ls->id_to_pdlv_poll; + + pdst_lock(pdst); + + pdlv = pdst_locked_lookup(pdst, id); + if (pdlv) { + /* + * with store lock held, I'm the only reader accessing the pdlv + */ + st = pdlv_get_status(pdlv); + + if (st.error || st.polling_finished) { + INFO(ls, "%s: %s %s", PD_LOG_PREFIX, + "Polling finished. Removing related data structure for LV", + lvid); + pdst_locked_remove(pdst, id); + pdlv_destroy(pdlv); + } + } + /* pdlv must not be dereferenced from now on */ + + pdst_unlock(pdst); + + dm_free(id); + + if (pdlv) { + if (st.error) + return reply(LVMPD_RESP_FAILED, REASON_POLLING_FAILED); + + if (st.polling_finished) + r = daemon_reply_simple(LVMPD_RESP_FINISHED, + "reason = %s", st.cmd_state.signal ? LVMPD_REAS_SIGNAL : LVMPD_REAS_RETCODE, + LVMPD_PARM_VALUE " = %d", (int64_t)(st.cmd_state.signal ?: st.cmd_state.retcode), + NULL); + else + r = daemon_reply_simple(LVMPD_RESP_IN_PROGRESS, NULL); + } + else + r = daemon_reply_simple(LVMPD_RESP_NOT_FOUND, NULL); + + return r; +} + +static struct lvmpolld_lv *construct_pdlv(request req, struct lvmpolld_state *ls, + struct lvmpolld_store *pdst, + const char *interval, const char *id, + const char *vgname, const char *lvname, + const char *sysdir, enum poll_type type, + unsigned abort_polling, unsigned uinterval) +{ + const char **cmdargv, **cmdenvp; + struct lvmpolld_lv *pdlv; + unsigned handle_missing_pvs = daemon_request_int(req, LVMPD_PARM_HANDLE_MISSING_PVS, 0); + + pdlv = pdlv_create(ls, id, vgname, lvname, sysdir, type, + interval, uinterval, pdst); + + if (!pdlv) { + ERROR(ls, "%s: %s", PD_LOG_PREFIX, "failed to create internal LV data structure."); + return NULL; + } + + cmdargv = cmdargv_ctr(pdlv, pdlv->ls->lvm_binary, abort_polling, handle_missing_pvs); + if (!cmdargv) { + pdlv_destroy(pdlv); + ERROR(ls, "%s: %s", PD_LOG_PREFIX, "failed to construct cmd arguments for lvpoll command"); + return NULL; + } + + pdlv->cmdargv = cmdargv; + + cmdenvp = cmdenvp_ctr(pdlv); + if (!cmdenvp) { + pdlv_destroy(pdlv); + ERROR(ls, "%s: %s", PD_LOG_PREFIX, "failed to construct cmd environment for lvpoll command"); + return NULL; + } + + pdlv->cmdenvp = cmdenvp; + + return pdlv; +} + +static int spawn_detached_thread(struct lvmpolld_lv *pdlv) +{ + int r; + pthread_attr_t attr; + + if (pthread_attr_init(&attr) != 0) + return 0; + + if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) != 0) + return 0; + + r = pthread_create(&pdlv->tid, &attr, fork_and_poll, (void *)pdlv); + + if (pthread_attr_destroy(&attr) != 0) + return 0; + + return !r; +} + +static response poll_init(client_handle h, struct lvmpolld_state *ls, request req, enum poll_type type) +{ + char *id; + struct lvmpolld_lv *pdlv; + struct lvmpolld_store *pdst; + unsigned uinterval; + + const char *interval = daemon_request_str(req, LVMPD_PARM_INTERVAL, NULL); + const char *lvid = daemon_request_str(req, LVMPD_PARM_LVID, NULL); + const char *lvname = daemon_request_str(req, LVMPD_PARM_LVNAME, NULL); + const char *vgname = daemon_request_str(req, LVMPD_PARM_VGNAME, NULL); + const char *sysdir = daemon_request_str(req, LVMPD_PARM_SYSDIR, NULL); + unsigned abort_polling = daemon_request_int(req, LVMPD_PARM_ABORT, 0); + + assert(type < POLL_TYPE_MAX); + + if (abort_polling && type != PVMOVE) + return reply(LVMPD_RESP_EINVAL, REASON_ILLEGAL_ABORT_REQUEST); + + if (!interval || strpbrk(interval, "-") || sscanf(interval, "%u", &uinterval) != 1) + return reply(LVMPD_RESP_EINVAL, REASON_INVALID_INTERVAL); + + if (!lvname) + return reply(LVMPD_RESP_FAILED, REASON_MISSING_LVNAME); + + if (!lvid) + return reply(LVMPD_RESP_FAILED, REASON_MISSING_LVID); + + if (!vgname) + return reply(LVMPD_RESP_FAILED, REASON_MISSING_VGNAME); + + id = construct_id(sysdir, lvid); + if (!id) { + ERROR(ls, "%s: %s", PD_LOG_PREFIX, "poll_init request failed to construct ID."); + return reply(LVMPD_RESP_FAILED, REASON_ENOMEM); + } + + DEBUGLOG(ls, "%s: %s=%s", PD_LOG_PREFIX, "ID", id); + + pdst = abort_polling ? ls->id_to_pdlv_abort : ls->id_to_pdlv_poll; + + pdst_lock(pdst); + + pdlv = pdst_locked_lookup(pdst, id); + if (pdlv && pdlv_get_polling_finished(pdlv)) { + WARN(ls, "%s: %s %s", PD_LOG_PREFIX, "Force removal of uncollected info for LV", + lvid); + /* + * lvmpolld has to remove uncollected results in this case. + * otherwise it would have to refuse request for new polling + * lv with same id. + */ + pdst_locked_remove(pdst, id); + pdlv_destroy(pdlv); + pdlv = NULL; + } + + if (pdlv) { + if (!pdlv_is_type(pdlv, type)) { + pdst_unlock(pdst); + ERROR(ls, "%s: %s '%s': expected: %s, requested: %s", + PD_LOG_PREFIX, "poll operation type mismatch on LV identified by", + id, + polling_op(pdlv_get_type(pdlv)), polling_op(type)); + dm_free(id); + return reply(LVMPD_RESP_EINVAL, + REASON_DIFFERENT_OPERATION_IN_PROGRESS); + } + pdlv->init_rq_count++; /* safe. protected by store lock */ + } else { + pdlv = construct_pdlv(req, ls, pdst, interval, id, vgname, + lvname, sysdir, type, abort_polling, 2 * uinterval); + if (!pdlv) { + pdst_unlock(pdst); + dm_free(id); + return reply(LVMPD_RESP_FAILED, REASON_ENOMEM); + } + if (!pdst_locked_insert(pdst, id, pdlv)) { + pdlv_destroy(pdlv); + pdst_unlock(pdst); + ERROR(ls, "%s: %s", PD_LOG_PREFIX, "couldn't store internal LV data structure"); + dm_free(id); + return reply(LVMPD_RESP_FAILED, REASON_ENOMEM); + } + if (!spawn_detached_thread(pdlv)) { + ERROR(ls, "%s: %s", PD_LOG_PREFIX, "failed to spawn detached monitoring thread"); + pdst_locked_remove(pdst, id); + pdlv_destroy(pdlv); + pdst_unlock(pdst); + dm_free(id); + return reply(LVMPD_RESP_FAILED, REASON_ENOMEM); + } + + pdst_locked_inc(pdst); + if (ls->idle) + ls->idle->is_idle = 0; + } + + pdst_unlock(pdst); + + dm_free(id); + + return daemon_reply_simple(LVMPD_RESP_OK, NULL); +} + +static response dump_state(client_handle h, struct lvmpolld_state *ls, request r) +{ + response res = { 0 }; + struct buffer *b = &res.buffer; + + buffer_init(b); + + _lvmpolld_global_lock(ls); + + buffer_append(b, "# Registered polling operations\n\n"); + buffer_append(b, "poll {\n"); + pdst_locked_dump(ls->id_to_pdlv_poll, b); + buffer_append(b, "}\n\n"); + + buffer_append(b, "# Registered abort operations\n\n"); + buffer_append(b, "abort {\n"); + pdst_locked_dump(ls->id_to_pdlv_abort, b); + buffer_append(b, "}"); + + _lvmpolld_global_unlock(ls); + + return res; +} + +static response _handler(struct daemon_state s, client_handle h, request r) +{ + struct lvmpolld_state *ls = s.private; + const char *rq = daemon_request_str(r, "request", "NONE"); + + if (!strcmp(rq, LVMPD_REQ_PVMOVE)) + return poll_init(h, ls, r, PVMOVE); + else if (!strcmp(rq, LVMPD_REQ_CONVERT)) + return poll_init(h, ls, r, CONVERT); + else if (!strcmp(rq, LVMPD_REQ_MERGE)) + return poll_init(h, ls, r, MERGE); + else if (!strcmp(rq, LVMPD_REQ_MERGE_THIN)) + return poll_init(h, ls, r, MERGE_THIN); + else if (!strcmp(rq, LVMPD_REQ_PROGRESS)) + return progress_info(h, ls, r); + else if (!strcmp(rq, LVMPD_REQ_DUMP)) + return dump_state(h, ls, r); + else + return reply(LVMPD_RESP_EINVAL, REASON_REQ_NOT_IMPLEMENTED); +} + +static int process_timeout_arg(const char *str, unsigned *max_timeouts) +{ + char *endptr; + unsigned long l; + + errno = 0; + l = strtoul(str, &endptr, 10); + if (errno || *endptr || l >= UINT_MAX) + return 0; + + *max_timeouts = (unsigned) l; + + return 1; +} + +/* Client functionality */ +typedef int (*action_fn_t) (void *args); + +struct log_line_baton { + const char *prefix; +}; + +daemon_handle _lvmpolld = { .error = 0 }; + +static daemon_handle _lvmpolld_open(const char *socket) +{ + daemon_info lvmpolld_info = { + .path = "lvmpolld", + .socket = socket ?: DEFAULT_RUN_DIR "/lvmpolld.socket", + .protocol = LVMPOLLD_PROTOCOL, + .protocol_version = LVMPOLLD_PROTOCOL_VERSION + }; + + return daemon_open(lvmpolld_info); +} + +static void _log_line(const char *line, void *baton) { + struct log_line_baton *b = baton; + fprintf(stdout, "%s%s\n", b->prefix, line); +} + +static int printout_raw_response(const char *prefix, const char *msg) +{ + struct log_line_baton b = { .prefix = prefix }; + char *buf; + char *pos; + + buf = dm_strdup(msg); + pos = buf; + + if (!buf) + return 0; + + while (pos) { + char *next = strchr(pos, '\n'); + if (next) + *next = 0; + _log_line(pos, &b); + pos = next ? next + 1 : 0; + } + dm_free(buf); + + return 1; +} + +/* place all action implementations below */ + +static int action_dump(void *args __attribute__((unused))) +{ + daemon_request req; + daemon_reply repl; + int r = 0; + + req = daemon_request_make(LVMPD_REQ_DUMP); + if (!req.cft) { + fprintf(stderr, "Failed to create lvmpolld " LVMPD_REQ_DUMP " request.\n"); + goto out_req; + } + + repl = daemon_send(_lvmpolld, req); + if (repl.error) { + fprintf(stderr, "Failed to send a request or receive response.\n"); + goto out_rep; + } + + /* + * This is dumb copy & paste from libdaemon log routines. + */ + if (!printout_raw_response(" ", repl.buffer.mem)) { + fprintf(stderr, "Failed to print out the response.\n"); + goto out_rep; + } + + r = 1; + +out_rep: + daemon_reply_destroy(repl); +out_req: + daemon_request_destroy(req); + + return r; +} + +enum action_index { + ACTION_DUMP = 0, + ACTION_MAX /* keep at the end */ +}; + +static const action_fn_t actions[ACTION_MAX] = { [ACTION_DUMP] = action_dump }; + +static int _make_action(enum action_index idx, void *args) +{ + return idx < ACTION_MAX ? actions[idx](args) : 0; +} + +static int _lvmpolld_client(const char *socket, unsigned action) +{ + int r; + + _lvmpolld = _lvmpolld_open(socket); + + if (_lvmpolld.error || _lvmpolld.socket_fd < 0) { + fprintf(stderr, "Failed to establish connection with lvmpolld.\n"); + return 0; + } + + r = _make_action(action, NULL); + + daemon_close(_lvmpolld); + + return r ? EXIT_SUCCESS : EXIT_FAILURE; +} + +static int action_idx = ACTION_MAX; +static struct option long_options[] = { + /* Have actions always at the beginning of the array. */ + {"dump", no_argument, &action_idx, ACTION_DUMP }, /* or an option_index ? */ + + /* other options */ + {"binary", required_argument, 0, 'B' }, + {"foreground", no_argument, 0, 'f' }, + {"help", no_argument, 0, 'h' }, + {"log", required_argument, 0, 'l' }, + {"pidfile", required_argument, 0, 'p' }, + {"socket", required_argument, 0, 's' }, + {"timeout", required_argument, 0, 't' }, + {"version", no_argument, 0, 'V' }, + {0, 0, 0, 0 } +}; + +int main(int argc, char *argv[]) +{ + int opt; + int option_index = 0; + int client = 0, server = 0; + unsigned action = ACTION_MAX; + struct timeval timeout; + daemon_idle di = { .ptimeout = &timeout }; + struct lvmpolld_state ls = { .log_config = "" }; + daemon_state s = { + .daemon_fini = _fini, + .daemon_init = _init, + .handler = _handler, + .name = "lvmpolld", + .pidfile = getenv("LVM_LVMPOLLD_PIDFILE") ?: LVMPOLLD_PIDFILE, + .private = &ls, + .protocol = LVMPOLLD_PROTOCOL, + .protocol_version = LVMPOLLD_PROTOCOL_VERSION, + .socket_path = getenv("LVM_LVMPOLLD_SOCKET") ?: LVMPOLLD_SOCKET, + }; + + while ((opt = getopt_long(argc, argv, "fhVl:p:s:B:t:", long_options, &option_index)) != -1) { + switch (opt) { + case 0 : + if (action < ACTION_MAX) { + fprintf(stderr, "Can't perform more actions. Action already requested: %s\n", + long_options[action].name); + _usage(argv[0], stderr); + exit(EXIT_FAILURE); + } + action = action_idx; + client = 1; + break; + case '?': + _usage(argv[0], stderr); + exit(EXIT_FAILURE); + case 'B': /* --binary */ + ls.lvm_binary = optarg; + server = 1; + break; + case 'V': /* --version */ + printf("lvmpolld version: " LVM_VERSION "\n"); + exit(EXIT_SUCCESS); + case 'f': /* --foreground */ + s.foreground = 1; + server = 1; + break; + case 'h': /* --help */ + _usage(argv[0], stdout); + exit(EXIT_SUCCESS); + case 'l': /* --log */ + ls.log_config = optarg; + server = 1; + break; + case 'p': /* --pidfile */ + s.pidfile = optarg; + server = 1; + break; + case 's': /* --socket */ + s.socket_path = optarg; + break; + case 't': /* --timeout in seconds */ + if (!process_timeout_arg(optarg, &di.max_timeouts)) { + fprintf(stderr, "Invalid value of timeout parameter.\n"); + exit(EXIT_FAILURE); + } + /* 0 equals to wait indefinitely */ + if (di.max_timeouts) + s.idle = ls.idle = &di; + server = 1; + break; + } + } + + if (client && server) { + fprintf(stderr, "Invalid combination of client and server parameters.\n\n"); + _usage(argv[0], stdout); + exit(EXIT_FAILURE); + } + + if (client) + return _lvmpolld_client(s.socket_path, action); + + /* Server */ + daemon_start(s); + + return EXIT_SUCCESS; +} |