summaryrefslogtreecommitdiff
path: root/daemons/lvmpolld/lvmpolld-core.c
diff options
context:
space:
mode:
Diffstat (limited to 'daemons/lvmpolld/lvmpolld-core.c')
-rw-r--r--daemons/lvmpolld/lvmpolld-core.c989
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;
+}