mirror of
git://sourceware.org/git/lvm2.git
synced 2024-12-22 17:35:59 +03:00
d2522f4a05
Enhance handling of interruptions of polling process and lvmpoll daemon. Daemon should now react much faster on interrups (i.e. shutdown sequence) and avoid taking lenghty sleep waiting on pvmove signaling.
1000 lines
26 KiB
C
1000 lines
26 KiB
C
/*
|
|
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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;
|
|
|
|
/*
|
|
* log warnings to stderr by default. Otherwise we would miss any lvpoll
|
|
* error messages in default configuration
|
|
*/
|
|
daemon_log_enable(ls->log, DAEMON_LOG_OUTLET_STDERR, DAEMON_LOG_WARN, 1);
|
|
|
|
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 = 10000000 }; /* .01 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) {
|
|
r = poll(fds, 2, pdlv_get_timeout(pdlv) * 1000);
|
|
|
|
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 */
|
|
WARN(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);
|
|
WARN(pdlv->ls, "%s: PID %d: %s: %s", LVM2_LOG_PREFIX, pdlv->cmd_pid, "STDERR", data->line);
|
|
}
|
|
|
|
if (WIFEXITED(ch_stat)) {
|
|
cmd_state.retcode = WEXITSTATUS(ch_stat);
|
|
if (cmd_state.retcode)
|
|
ERROR(pdlv->ls, "%s: %s (PID %d) %s (retcode: %d)", PD_LOG_PREFIX,
|
|
"lvm2 cmd", pdlv->cmd_pid, "failed", cmd_state.retcode);
|
|
else
|
|
INFO(pdlv->ls, "%s: %s (PID %d) %s", PD_LOG_PREFIX,
|
|
"lvm2 cmd", pdlv->cmd_pid, "finished successfully");
|
|
} else if (WIFSIGNALED(ch_stat)) {
|
|
ERROR(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);
|
|
|
|
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 " = " FMTd64, (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 *devicesfile)
|
|
{
|
|
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, devicesfile);
|
|
|
|
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);
|
|
const char *devicesfile = daemon_request_str(req, LVMPD_PARM_DEVICESFILE, 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));
|
|
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, devicesfile);
|
|
if (!pdlv) {
|
|
pdst_unlock(pdst);
|
|
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");
|
|
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);
|
|
free(id);
|
|
return reply(LVMPD_RESP_FAILED, REASON_ENOMEM);
|
|
}
|
|
|
|
pdst_locked_inc(pdst);
|
|
if (ls->idle)
|
|
ls->idle->is_idle = 0;
|
|
}
|
|
|
|
pdst_unlock(pdst);
|
|
|
|
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 = 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;
|
|
}
|
|
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 timespec 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;
|
|
}
|