1
0
mirror of git://sourceware.org/git/lvm2.git synced 2024-12-21 13:34:40 +03:00

scripts/lvm2_activation_generator_systemd_red_hat: rewrite to use lvmconfig

Unit tested the new code, but not run functional tests (assuming they exist).
This commit is contained in:
Joe Thornber 2018-06-07 16:15:04 +01:00
parent 74460cd009
commit bd8c6cf862
6 changed files with 352 additions and 115 deletions

View File

@ -1533,6 +1533,9 @@ SBINDIR="$(eval echo $(eval echo $sbindir))"
LVM_PATH="$SBINDIR/lvm"
AC_DEFINE_UNQUOTED(LVM_PATH, ["$LVM_PATH"], [Path to lvm binary.])
LVMCONFIG_PATH="$$BINDIR/lvmconfig"
AC_DEFINE_UNQUOTED(LVMCONFIG_PATH, ["$LVMCONFIG_PATH"], [Path to lvmconfig binary.])
USRSBINDIR="$(eval echo $(eval echo $usrsbindir))"
FSADM_PATH="$SBINDIR/fsadm"

View File

@ -15,21 +15,14 @@ srcdir = @srcdir@
top_srcdir = @top_srcdir@
top_builddir = @top_builddir@
ifeq ("@APPLIB@", "yes")
SOURCES = lvm2_activation_generator_systemd_red_hat.c
TARGETS = lvm2_activation_generator_systemd_red_hat
endif
SOURCES = lvm2_activation_generator_systemd_red_hat.c
TARGETS = lvm2_activation_generator_systemd_red_hat
include $(top_builddir)/make.tmpl
ifeq ("@APPLIB@", "yes")
DEPLIBS += $(top_builddir)/liblvm/liblvm2app.so
LDFLAGS += -L$(top_builddir)/liblvm
ifeq ("@BUILD_DMEVENTD@", "yes")
LDFLAGS += -Wl,-rpath-link,$(top_builddir)/daemons/dmeventd
endif
LVMLIBS = @LVM2APP_LIB@ -laio
endif
LVM_SCRIPTS = lvmdump.sh lvmconf.sh
DM_SCRIPTS =
@ -93,12 +86,7 @@ lvm2_activation_generator_systemd_red_hat: $(OBJECTS) $(DEPLIBS) $(INTERNAL_LIBS
install_systemd_generators:
$(INSTALL_DIR) $(systemd_generator_dir)
ifeq ("@APPLIB@", "yes")
$(INSTALL_PROGRAM) lvm2_activation_generator_systemd_red_hat $(systemd_generator_dir)/lvm2-activation-generator
else
@echo "WARNING: LVM2 activation systemd generator not installed." \
"It requires the LVM2 application library to be built as well."
endif
install_systemd_units: install_dbus_service
$(INSTALL_DIR) $(systemd_unit_dir)

View File

@ -0,0 +1,200 @@
// This file contains the unit testable parts of
// lvm2_activation_generator_systemd_red_hat
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h> /* For PATH_MAX for musl libc */
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
//----------------------------------------------------------------
static void _error(const char *format, ...) __attribute__ ((format(printf, 1, 2)));
//----------------------------------------------------------------
// I'm rolling my own version of popen() here because I do not want to
// go through the shell.
struct child_process {
pid_t pid;
FILE *fp;
};
static bool _open_child(struct child_process *child, const char *cmd, const char *argv[])
{
int r, pipe_fd[2];
r = pipe(pipe_fd);
if (r < 0) {
_error("call to pipe() failed: %d\n", r);
return false;
}
child->pid = fork();
if (child->pid < 0) {
close(pipe_fd[0]);
close(pipe_fd[1]);
_error("call to fork() failed: %d\n", r);
return false;
}
if (child->pid == 0) {
// child
close(pipe_fd[0]);
if (pipe_fd[1] != STDOUT_FILENO) {
dup2(pipe_fd[1], STDOUT_FILENO);
close(pipe_fd[1]);
}
execv(cmd, (char *const *) argv);
// Shouldn't get here unless exec failed.
exit(1);
} else {
// parent
close(pipe_fd[1]);
child->fp = fdopen(pipe_fd[0], "r");
if (!child->fp) {
_error("call to fdopen() failed\n");
return false;
}
}
return true;
}
// Returns the child's exit status
static bool _close_child(struct child_process *child)
{
int status;
fclose(child->fp);
while (waitpid(child->pid, &status, 0) < 0)
if (errno != EINTR)
return -1;
if (WIFEXITED(status) && !WEXITSTATUS(status))
return true;
return false;
}
//----------------------------------------------------------------
// Aquiring config from the lvmconfig process
#define LVM_CONF_USE_LVMETAD "global/use_lvmetad"
#define LVM_CONF_USE_LVMPOLLD "global/use_lvmpolld"
struct config {
bool use_lvmetad;
bool sysinit_needed;
};
static bool _begins_with(const char *line, const char *prefix, const char **rest)
{
size_t len = strlen(prefix);
if (strlen(line) < len)
return false;
if (strncmp(line, prefix, len))
return false;
*rest = line + len;
return true;
}
static bool _parse_bool(const char *val, bool * result)
{
const char *b = val, *e;
while (*b && isspace(*b))
b++;
if (!*b)
goto parse_error;
e = b;
while (*e && !isspace(*e))
e++;
if ((e - b) != 1)
goto parse_error;
// We only handle '1', or '0'
if (*b == '1') {
*result = true;
return true;
} else if (*b == '0') {
*result = false;
return true;
}
// Fallthrough
parse_error:
_error("couldn't parse bool value '%s'\n", val);
return false;
}
static bool _parse_line(const char *line, struct config *cfg)
{
const char *val;
if (_begins_with(line, "use_lvmetad=", &val)) {
return _parse_bool(val, &cfg->use_lvmetad);
} else if (_begins_with(line, "use_lvmpolld=", &val)) {
bool r;
if (!_parse_bool(val, &r))
return false;
cfg->sysinit_needed = !r;
return true;
}
return false;
}
static bool _get_config(struct config *cfg, const char *lvmconfig_path)
{
static const char *_argv[] = {
"lvmconfig", LVM_CONF_USE_LVMETAD, LVM_CONF_USE_LVMPOLLD, NULL
};
bool r = true;
char buffer[256];
struct child_process child;
cfg->use_lvmetad = false;
cfg->sysinit_needed = true;
if (!_open_child(&child, lvmconfig_path, _argv)) {
_error("couldn't open lvmconfig process\n");
return false;
}
while (fgets(buffer, sizeof(buffer), child.fp)) {
if (!_parse_line(buffer, cfg)) {
_error("_parse_line() failed\n");
r = false;
}
}
if (!_close_child(&child)) {
_error("lvmconfig failed\n");
r = false;
}
return r;
}

View File

@ -12,29 +12,79 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <stdio.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <stdarg.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <limits.h> /* For PATH_MAX for musl libc */
#include "liblvm/lvm2app.h"
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "configure.h"
#include "device_mapper/libdevmapper.h"
//----------------------------------------------------------------
// Code in this file gets included in the unit tests.
#include "generator-internals.c"
//----------------------------------------------------------------
// Logging
#define KMSG_DEV_PATH "/dev/kmsg"
#define LVM_CONF_USE_LVMETAD "global/use_lvmetad"
#define LVM_CONF_USE_LVMPOLLD "global/use_lvmpolld"
static int _kmsg_fd;
static void _log_init(void)
{
// failing is harmless
_kmsg_fd = open(KMSG_DEV_PATH, O_WRONLY | O_NOCTTY);
}
static void _log_exit(void)
{
if (_kmsg_fd != -1)
(void) close(_kmsg_fd);
}
__attribute__ ((format(printf, 1, 2)))
static void _error(const char *format, ...)
{
int n;
va_list ap;
char message[PATH_MAX + 3]; /* +3 for '<n>' where n is the log level */
snprintf(message, 4, "<%d>", LOG_ERR);
va_start(ap, format);
n = vsnprintf(message + 3, PATH_MAX, format, ap);
va_end(ap);
if (_kmsg_fd < 0 || (n < 0 || ((unsigned) n + 1 > PATH_MAX)))
return;
/* The n+4: +3 for "<n>" prefix and +1 for '\0' suffix */
(void) write(_kmsg_fd, message, n + 4);
}
//----------------------------------------------------------------
#define UNIT_TARGET_LOCAL_FS "local-fs-pre.target"
#define UNIT_TARGET_REMOTE_FS "remote-fs-pre.target"
static char unit_path[PATH_MAX];
static char target_path[PATH_MAX];
static char message[PATH_MAX + 3]; /* +3 for '<n>' where n is the log level */
static int kmsg_fd = -1;
struct generator {
const char *dir;
struct config cfg;
int kmsg_fd;
char unit_path[PATH_MAX];
char target_path[PATH_MAX];
};
enum {
UNIT_EARLY,
@ -42,76 +92,59 @@ enum {
UNIT_NET
};
static const char *unit_names[] = {
static const char *_unit_names[] = {
[UNIT_EARLY] = "lvm2-activation-early.service",
[UNIT_MAIN] = "lvm2-activation.service",
[UNIT_NET] = "lvm2-activation-net.service"
};
__attribute__ ((format(printf, 2, 3)))
static void kmsg(int log_level, const char *format, ...)
{
va_list ap;
int n;
//----------------------------------------------------------------
snprintf(message, 4, "<%d>", log_level);
va_start(ap, format);
n = vsnprintf(message + 3, PATH_MAX, format, ap);
va_end(ap);
if (kmsg_fd < 0 || (n < 0 || ((unsigned) n + 1 > PATH_MAX)))
return;
/* The n+4: +3 for "<n>" prefix and +1 for '\0' suffix */
if (write(kmsg_fd, message, n + 4)) { /* Ignore result code */; }
}
static void lvm_get_use_lvmetad_and_lvmpolld(int *use_lvmetad, int *use_lvmpolld)
{
*use_lvmetad = *use_lvmpolld = 0;
*use_lvmetad = lvm_config_find_bool(NULL, LVM_CONF_USE_LVMETAD, 0);
*use_lvmpolld = lvm_config_find_bool(NULL, LVM_CONF_USE_LVMPOLLD, 0);
}
static int register_unit_with_target(const char *dir, const char *unit, const char *target)
static int register_unit_with_target(struct generator *gen, const char *unit,
const char *target)
{
int r = 1;
if (dm_snprintf(target_path, PATH_MAX, "%s/%s.wants", dir, target) < 0) {
r = 0; goto out;
}
(void) dm_prepare_selinux_context(target_path, S_IFDIR);
if (mkdir(target_path, 0755) < 0 && errno != EEXIST) {
kmsg(LOG_ERR, "LVM: Failed to create target directory %s: %m.\n", target_path);
r = 0; goto out;
if (dm_snprintf(gen->target_path, PATH_MAX, "%s/%s.wants", gen->dir, target) < 0) {
r = 0;
goto out;
}
if (dm_snprintf(target_path, PATH_MAX, "%s/%s.wants/%s", dir, target, unit) < 0) {
r = 0; goto out;
(void) dm_prepare_selinux_context(gen->target_path, S_IFDIR);
if (mkdir(gen->target_path, 0755) < 0 && errno != EEXIST) {
_error("LVM: Failed to create target directory %s: %m.\n", gen->target_path);
r = 0;
goto out;
}
(void) dm_prepare_selinux_context(target_path, S_IFLNK);
if (symlink(unit_path, target_path) < 0) {
kmsg(LOG_ERR, "LVM: Failed to create symlink for unit %s: %m.\n", unit);
if (dm_snprintf
(gen->target_path, PATH_MAX, "%s/%s.wants/%s", gen->dir, target, unit) < 0) {
r = 0;
goto out;
}
(void) dm_prepare_selinux_context(gen->target_path, S_IFLNK);
if (symlink(gen->unit_path, gen->target_path) < 0) {
_error("LVM: Failed to create symlink for unit %s: %m.\n", unit);
r = 0;
}
out:
out:
dm_prepare_selinux_context(NULL, 0);
return r;
}
static int generate_unit(const char *dir, int unit, int sysinit_needed)
static int generate_unit(struct generator *gen, int unit)
{
FILE *f;
const char *unit_name = unit_names[unit];
const char *target_name = unit == UNIT_NET ? UNIT_TARGET_REMOTE_FS : UNIT_TARGET_LOCAL_FS;
const char *unit_name = _unit_names[unit];
const char *target_name =
unit == UNIT_NET ? UNIT_TARGET_REMOTE_FS : UNIT_TARGET_LOCAL_FS;
if (dm_snprintf(unit_path, PATH_MAX, "%s/%s", dir, unit_name) < 0)
if (dm_snprintf(gen->unit_path, PATH_MAX, "%s/%s", gen->dir, unit_name)
< 0)
return 0;
if (!(f = fopen(unit_path, "wxe"))) {
kmsg(LOG_ERR, "LVM: Failed to create unit file %s: %m.\n", unit_name);
if (!(f = fopen(gen->unit_path, "wxe"))) {
_error("LVM: Failed to create unit file %s: %m.\n", unit_name);
return 0;
}
@ -124,79 +157,90 @@ static int generate_unit(const char *dir, int unit, int sysinit_needed)
"[Unit]\n"
"Description=Activation of LVM2 logical volumes\n"
"Documentation=man:lvm2-activation-generator(8)\n"
"SourcePath=/etc/lvm/lvm.conf\n"
"DefaultDependencies=no\n", f);
"SourcePath=/etc/lvm/lvm.conf\n" "DefaultDependencies=no\n", f);
if (unit == UNIT_NET) {
fprintf(f, "After=%s iscsi.service fcoe.service\n"
"Before=remote-fs-pre.target shutdown.target\n\n"
"[Service]\n"
"ExecStartPre=/usr/bin/udevadm settle\n", unit_names[UNIT_MAIN]);
"ExecStartPre=/usr/bin/udevadm settle\n", _unit_names[UNIT_MAIN]);
} else {
if (unit == UNIT_EARLY) {
if (unit == UNIT_EARLY)
fputs("After=systemd-udev-settle.service\n"
"Before=cryptsetup.target\n", f);
} else
fprintf(f, "After=%s cryptsetup.target\n", unit_names[UNIT_EARLY]);
else
fprintf(f, "After=%s cryptsetup.target\n", _unit_names[UNIT_EARLY]);
fputs("Before=local-fs-pre.target shutdown.target\n"
"Wants=systemd-udev-settle.service\n\n"
"[Service]\n", f);
"Wants=systemd-udev-settle.service\n\n" "[Service]\n", f);
}
fputs("ExecStart=" LVM_PATH " vgchange -aay --ignoreskippedcluster", f);
if (sysinit_needed)
fputs (" --sysinit", f);
if (gen->cfg.sysinit_needed)
fputs(" --sysinit", f);
fputs("\nType=oneshot\n", f);
if (fclose(f) < 0) {
kmsg(LOG_ERR, "LVM: Failed to write unit file %s: %m.\n", unit_name);
_error("LVM: Failed to write unit file %s: %m.\n", unit_name);
return 0;
}
if (!register_unit_with_target(dir, unit_name, target_name)) {
kmsg(LOG_ERR, "LVM: Failed to register unit %s with target %s.\n", unit_name, target_name);
if (!register_unit_with_target(gen, unit_name, target_name)) {
_error("LVM: Failed to register unit %s with target %s.\n",
unit_name, target_name);
return 0;
}
return 1;
}
int main(int argc, char *argv[])
static bool _parse_command_line(struct generator *gen, int argc, const char **argv)
{
int use_lvmetad, use_lvmpolld, sysinit_needed;
const char *dir;
int r = EXIT_SUCCESS;
mode_t old_mask;
kmsg_fd = open(KMSG_DEV_PATH, O_WRONLY|O_NOCTTY);
if (argc != 4) {
kmsg(LOG_ERR, "LVM: Incorrect number of arguments for activation generator.\n");
r = EXIT_FAILURE; goto out;
_error("LVM: Incorrect number of arguments for activation generator.\n");
return false;
}
/* If lvmetad used, rely on autoactivation instead of direct activation. */
lvm_get_use_lvmetad_and_lvmpolld(&use_lvmetad, &use_lvmpolld);
if (use_lvmetad)
goto out;
gen->dir = argv[1];
return true;
}
dir = argv[1];
static bool _run(int argc, const char **argv)
{
bool r;
mode_t old_mask;
struct generator gen;
if (!_parse_command_line(&gen, argc, argv))
return false;
if (!_get_config(&gen.cfg, LVMCONFIG_PATH))
return false;
if (gen.cfg.use_lvmetad)
// If lvmetad used, rely on autoactivation instead of direct activation.
return true;
/* mark lvm2-activation.*.service as world-accessible */
old_mask = umask(0022);
sysinit_needed = !use_lvmpolld;
r = generate_unit(&gen, UNIT_EARLY) &&
generate_unit(&gen, UNIT_MAIN) && generate_unit(&gen, UNIT_NET);
if (!generate_unit(dir, UNIT_EARLY, sysinit_needed) ||
!generate_unit(dir, UNIT_MAIN, sysinit_needed) ||
!generate_unit(dir, UNIT_NET, sysinit_needed))
r = EXIT_FAILURE;
umask(old_mask);
out:
if (r)
kmsg(LOG_ERR, "LVM: Activation generator failed.\n");
if (kmsg_fd != -1)
(void) close(kmsg_fd);
return r;
}
int main(int argc, const char **argv)
{
bool r;
_log_init();
r = _run(argc, argv);
if (!r)
_error("LVM: Activation generator failed.\n");
_log_exit();
return r ? EXIT_SUCCESS : EXIT_FAILURE;
}

View File

@ -14,6 +14,7 @@ UNIT_SOURCE=\
base/data-struct/radix-tree.c \
device_mapper/vdo/status.c \
\
test/unit/activation-generator_t.c \
test/unit/bcache_t.c \
test/unit/bcache_utils_t.c \
test/unit/bitset_t.c \
@ -32,12 +33,11 @@ UNIT_SOURCE=\
UNIT_DEPENDS=$(subst .c,.d,$(UNIT_SOURCE))
UNIT_OBJECTS=$(UNIT_SOURCE:%.c=%.o)
CLEAN_TARGETS+=$(UNIT_DEPENDS) $(UNIT_OBJECTS)
UNIT_LDLIBS += $(LVMINTERNAL_LIBS) -laio
test/unit/unit-test: $(UNIT_OBJECTS) lib/liblvm-internal.a $(INTERNAL_LIBS)
test/unit/unit-test: $(UNIT_OBJECTS) lib/liblvm-internal.a libdaemon/client/libdaemonclient.a $(INTERNAL_LIBS)
@echo " [LD] $@"
$(Q) $(CC) $(CFLAGS) $(LDFLAGS) $(EXTRA_EXEC_LDFLAGS) \
-o $@ $+ $(UNIT_LDLIBS) -lm
-o $@ $+ $(LIBS) -lm -ldl -laio
.PHONEY: run-unit-test
run-unit-test: test/unit/unit-test

View File

@ -20,6 +20,7 @@
//-----------------------------------------------------------------
// Declare the function that adds tests suites here ...
void activation_generator_tests(struct dm_list *suites);
void bcache_tests(struct dm_list *suites);
void bcache_utils_tests(struct dm_list *suites);
void bitset_tests(struct dm_list *suites);
@ -36,6 +37,7 @@ void vdo_tests(struct dm_list *suites);
// ... and call it in here.
static inline void register_all_tests(struct dm_list *suites)
{
activation_generator_tests(suites);
bcache_tests(suites);
bcache_utils_tests(suites);
bitset_tests(suites);