| /* |
| * Copyright (C) 2011-2016 Red Hat, Inc. All rights reserved. |
| * |
| * 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 "lib.h" /* using here lvm log */ |
| #include "dmeventd_lvm.h" |
| #include "libdevmapper-event.h" |
| |
| #include <sys/wait.h> |
| #include <stdarg.h> |
| #include <pthread.h> |
| |
| /* TODO - move this mountinfo code into library to be reusable */ |
| #ifdef __linux__ |
| # include "kdev_t.h" |
| #else |
| # define MAJOR(x) major((x)) |
| # define MINOR(x) minor((x)) |
| #endif |
| |
| /* First warning when thin data or metadata is 80% full. */ |
| #define WARNING_THRESH (DM_PERCENT_1 * 80) |
| /* Umount thin LVs when thin data or metadata LV is >= |
| * and lvextend --use-policies has failed. */ |
| #define UMOUNT_THRESH (DM_PERCENT_1 * 95) |
| /* Run a check every 5%. */ |
| #define CHECK_STEP (DM_PERCENT_1 * 5) |
| /* Do not bother checking thin data or metadata is less than 50% full. */ |
| #define CHECK_MINIMUM (DM_PERCENT_1 * 50) |
| |
| #define UMOUNT_COMMAND "/bin/umount" |
| |
| #define MAX_FAILS (10) |
| |
| #define THIN_DEBUG 0 |
| |
| struct dso_state { |
| struct dm_pool *mem; |
| int metadata_percent_check; |
| int data_percent_check; |
| uint64_t known_metadata_size; |
| uint64_t known_data_size; |
| unsigned fails; |
| char cmd_str[1024]; |
| }; |
| |
| DM_EVENT_LOG_FN("thin") |
| |
| #define UUID_PREFIX "LVM-" |
| |
| /* Figure out device UUID has LVM- prefix and is OPEN */ |
| static int _has_unmountable_prefix(int major, int minor) |
| { |
| struct dm_task *dmt; |
| struct dm_info info; |
| const char *uuid; |
| int r = 0; |
| |
| if (!(dmt = dm_task_create(DM_DEVICE_INFO))) |
| return_0; |
| |
| if (!dm_task_set_major_minor(dmt, major, minor, 1)) |
| goto_out; |
| |
| if (!dm_task_no_flush(dmt)) |
| stack; |
| |
| if (!dm_task_run(dmt)) |
| goto out; |
| |
| if (!dm_task_get_info(dmt, &info)) |
| goto out; |
| |
| if (!info.exists || !info.open_count) |
| goto out; /* Not open -> not mounted */ |
| |
| if (!(uuid = dm_task_get_uuid(dmt))) |
| goto out; |
| |
| /* Check it's public mountable LV |
| * has prefix LVM- and UUID size is 68 chars */ |
| if (memcmp(uuid, UUID_PREFIX, sizeof(UUID_PREFIX) - 1) || |
| strlen(uuid) != 68) |
| goto out; |
| |
| #if THIN_DEBUG |
| log_debug("Found logical volume %s (%u:%u).", uuid, major, minor); |
| #endif |
| r = 1; |
| out: |
| dm_task_destroy(dmt); |
| |
| return r; |
| } |
| |
| /* Get dependencies for device, and try to find matching device */ |
| static int _has_deps(const char *name, int tp_major, int tp_minor, int *dev_minor) |
| { |
| struct dm_task *dmt; |
| const struct dm_deps *deps; |
| struct dm_info info; |
| int major, minor; |
| int r = 0; |
| |
| if (!(dmt = dm_task_create(DM_DEVICE_DEPS))) |
| return 0; |
| |
| if (!dm_task_set_name(dmt, name)) |
| goto out; |
| |
| if (!dm_task_no_open_count(dmt)) |
| goto out; |
| |
| if (!dm_task_run(dmt)) |
| goto out; |
| |
| if (!dm_task_get_info(dmt, &info)) |
| goto out; |
| |
| if (!(deps = dm_task_get_deps(dmt))) |
| goto out; |
| |
| if (!info.exists || deps->count != 1) |
| goto out; |
| |
| major = (int) MAJOR(deps->device[0]); |
| minor = (int) MINOR(deps->device[0]); |
| if ((major != tp_major) || (minor != tp_minor)) |
| goto out; |
| |
| *dev_minor = info.minor; |
| |
| if (!_has_unmountable_prefix(major, info.minor)) |
| goto out; |
| |
| #if THIN_DEBUG |
| { |
| char dev_name[PATH_MAX]; |
| if (dm_device_get_name(major, minor, 0, dev_name, sizeof(dev_name))) |
| log_debug("Found %s (%u:%u) depends on %s.", |
| name, major, *dev_minor, dev_name); |
| } |
| #endif |
| r = 1; |
| out: |
| dm_task_destroy(dmt); |
| |
| return r; |
| } |
| |
| /* Get all active devices */ |
| static int _find_all_devs(dm_bitset_t bs, int tp_major, int tp_minor) |
| { |
| struct dm_task *dmt; |
| struct dm_names *names; |
| unsigned next = 0; |
| int minor, r = 1; |
| |
| if (!(dmt = dm_task_create(DM_DEVICE_LIST))) |
| return 0; |
| |
| if (!dm_task_run(dmt)) { |
| r = 0; |
| goto out; |
| } |
| |
| if (!(names = dm_task_get_names(dmt))) { |
| r = 0; |
| goto out; |
| } |
| |
| if (!names->dev) |
| goto out; |
| |
| do { |
| names = (struct dm_names *)((char *) names + next); |
| if (_has_deps(names->name, tp_major, tp_minor, &minor)) |
| dm_bit_set(bs, minor); |
| next = names->next; |
| } while (next); |
| |
| out: |
| dm_task_destroy(dmt); |
| |
| return r; |
| } |
| |
| static int _run(const char *cmd, ...) |
| { |
| va_list ap; |
| int argc = 1; /* for argv[0], i.e. cmd */ |
| int i = 0; |
| const char **argv; |
| pid_t pid = fork(); |
| int status; |
| |
| if (pid == 0) { /* child */ |
| va_start(ap, cmd); |
| while (va_arg(ap, const char *)) |
| ++argc; |
| va_end(ap); |
| |
| /* + 1 for the terminating NULL */ |
| argv = alloca(sizeof(const char *) * (argc + 1)); |
| |
| argv[0] = cmd; |
| va_start(ap, cmd); |
| while ((argv[++i] = va_arg(ap, const char *))); |
| va_end(ap); |
| |
| execvp(cmd, (char **)argv); |
| log_sys_error("exec", cmd); |
| exit(127); |
| } |
| |
| if (pid > 0) { /* parent */ |
| if (waitpid(pid, &status, 0) != pid) |
| return 0; /* waitpid failed */ |
| if (!WIFEXITED(status) || WEXITSTATUS(status)) |
| return 0; /* the child failed */ |
| } |
| |
| if (pid < 0) |
| return 0; /* fork failed */ |
| |
| return 1; /* all good */ |
| } |
| |
| struct mountinfo_s { |
| const char *device; |
| struct dm_info info; |
| dm_bitset_t minors; /* Bitset for active thin pool minors */ |
| }; |
| |
| static int _umount_device(char *buffer, unsigned major, unsigned minor, |
| char *target, void *cb_data) |
| { |
| struct mountinfo_s *data = cb_data; |
| char *words[10]; |
| |
| if ((major == data->info.major) && dm_bit(data->minors, minor)) { |
| if (dm_split_words(buffer, DM_ARRAY_SIZE(words), 0, words) < DM_ARRAY_SIZE(words)) |
| words[9] = NULL; /* just don't show device name */ |
| log_info("Unmounting thin %s (%d:%d) of thin-pool %s (%u:%u) from mount point \"%s\".", |
| words[9] ? : "", major, minor, data->device, |
| data->info.major, data->info.minor, |
| target); |
| if (!_run(UMOUNT_COMMAND, "-fl", target, NULL)) |
| log_error("Failed to lazy umount thin %s (%d:%d) from %s: %s.", |
| words[9], major, minor, target, strerror(errno)); |
| } |
| |
| return 1; |
| } |
| |
| /* |
| * Find all thin pool LV users and try to umount them. |
| * TODO: work with read-only thin pool support |
| */ |
| static void _umount(struct dm_task *dmt) |
| { |
| /* TODO: Convert to use hash to reduce memory usage */ |
| static const size_t MINORS = (1U << 20); /* 20 bit */ |
| struct mountinfo_s data = { NULL }; |
| |
| if (!dm_task_get_info(dmt, &data.info)) |
| return; |
| |
| data.device = dm_task_get_name(dmt); |
| |
| if (!(data.minors = dm_bitset_create(NULL, MINORS))) { |
| log_error("Failed to allocate bitset. Not unmounting %s.", data.device); |
| goto out; |
| } |
| |
| if (!_find_all_devs(data.minors, data.info.major, data.info.minor)) { |
| log_error("Failed to detect mounted volumes for %s.", data.device); |
| goto out; |
| } |
| |
| if (!dm_mountinfo_read(_umount_device, &data)) { |
| log_error("Could not parse mountinfo file."); |
| goto out; |
| } |
| |
| out: |
| if (data.minors) |
| dm_bitset_destroy(data.minors); |
| } |
| |
| static int _use_policy(struct dm_task *dmt, struct dso_state *state) |
| { |
| #if THIN_DEBUG |
| log_info("dmeventd executes: %s.", state->cmd_str); |
| #endif |
| if (!dmeventd_lvm2_run_with_lock(state->cmd_str)) { |
| log_error("Failed to extend thin pool %s.", |
| dm_task_get_name(dmt)); |
| state->fails++; |
| return 0; |
| } |
| |
| state->fails = 0; |
| return 1; |
| } |
| |
| void process_event(struct dm_task *dmt, |
| enum dm_event_mask event __attribute__((unused)), |
| void **user) |
| { |
| const char *device = dm_task_get_name(dmt); |
| int percent; |
| struct dso_state *state = *user; |
| struct dm_status_thin_pool *tps = NULL; |
| void *next = NULL; |
| uint64_t start, length; |
| char *target_type = NULL; |
| char *params; |
| int needs_policy = 0; |
| int needs_umount = 0; |
| |
| #if THIN_DEBUG |
| log_debug("Watch for tp-data:%.2f%% tp-metadata:%.2f%%.", |
| dm_percent_to_float(state->data_percent_check), |
| dm_percent_to_float(state->metadata_percent_check)); |
| #endif |
| |
| #if 0 |
| /* No longer monitoring, waiting for remove */ |
| if (!state->meta_percent_check && !state->data_percent_check) |
| return; |
| #endif |
| if (event & DM_EVENT_DEVICE_ERROR) { |
| /* Error -> no need to check and do instant resize */ |
| if (_use_policy(dmt, state)) |
| goto out; |
| |
| stack; |
| } |
| |
| dm_get_next_target(dmt, next, &start, &length, &target_type, ¶ms); |
| |
| if (!target_type || (strcmp(target_type, "thin-pool") != 0)) { |
| log_error("Invalid target type."); |
| goto out; |
| } |
| |
| if (!dm_get_status_thin_pool(state->mem, params, &tps)) { |
| log_error("Failed to parse status."); |
| needs_umount = 1; |
| goto out; |
| } |
| |
| #if THIN_DEBUG |
| log_debug("Thin pool status " FMTu64 "/" FMTu64 " " |
| FMTu64 "/" FMTu64 ".", |
| tps->used_metadata_blocks, tps->total_metadata_blocks, |
| tps->used_data_blocks, tps->total_data_blocks); |
| #endif |
| |
| /* Thin pool size had changed. Clear the threshold. */ |
| if (state->known_metadata_size != tps->total_metadata_blocks) { |
| state->metadata_percent_check = CHECK_MINIMUM; |
| state->known_metadata_size = tps->total_metadata_blocks; |
| } |
| |
| if (state->known_data_size != tps->total_data_blocks) { |
| state->data_percent_check = CHECK_MINIMUM; |
| state->known_data_size = tps->total_data_blocks; |
| } |
| |
| percent = dm_make_percent(tps->used_metadata_blocks, tps->total_metadata_blocks); |
| if (percent >= state->metadata_percent_check) { |
| /* |
| * Usage has raised more than CHECK_STEP since the last |
| * time. Run actions. |
| */ |
| state->metadata_percent_check = (percent / CHECK_STEP) * CHECK_STEP + CHECK_STEP; |
| |
| /* FIXME: extension of metadata needs to be written! */ |
| if (percent >= WARNING_THRESH) /* Print a warning to syslog. */ |
| log_warn("WARNING: Thin pool %s metadata is now %.2f%% full.", |
| device, dm_percent_to_float(percent)); |
| needs_policy = 1; |
| |
| if (percent >= UMOUNT_THRESH) |
| needs_umount = 1; |
| } |
| |
| percent = dm_make_percent(tps->used_data_blocks, tps->total_data_blocks); |
| if (percent >= state->data_percent_check) { |
| /* |
| * Usage has raised more than CHECK_STEP since |
| * the last time. Run actions. |
| */ |
| state->data_percent_check = (percent / CHECK_STEP) * CHECK_STEP + CHECK_STEP; |
| |
| if (percent >= WARNING_THRESH) /* Print a warning to syslog. */ |
| log_warn("WARNING: Thin pool %s data is now %.2f%% full.", |
| device, dm_percent_to_float(percent)); |
| needs_policy = 1; |
| |
| if (percent >= UMOUNT_THRESH) |
| needs_umount = 1; |
| } |
| |
| if (needs_policy && |
| _use_policy(dmt, state)) |
| needs_umount = 0; /* No umount when command was successful */ |
| out: |
| if (needs_umount) { |
| _umount(dmt); |
| /* Until something changes, do not retry any more actions */ |
| state->data_percent_check = state->metadata_percent_check = (DM_PERCENT_1 * 101); |
| } |
| |
| if (tps) |
| dm_pool_free(state->mem, tps); |
| |
| if (state->fails >= MAX_FAILS) { |
| log_warn("WARNING: Dropping monitoring of %s. " |
| "lvm2 command fails too often (%u times in raw).", |
| device, state->fails); |
| pthread_kill(pthread_self(), SIGALRM); |
| } |
| } |
| |
| int register_device(const char *device, |
| const char *uuid __attribute__((unused)), |
| int major __attribute__((unused)), |
| int minor __attribute__((unused)), |
| void **user) |
| { |
| struct dso_state *state; |
| |
| if (!dmeventd_lvm2_init_with_pool("thin_pool_state", state)) |
| goto_bad; |
| |
| if (!dmeventd_lvm2_command(state->mem, state->cmd_str, |
| sizeof(state->cmd_str), |
| "lvextend --use-policies", |
| device)) { |
| dmeventd_lvm2_exit_with_pool(state); |
| goto_bad; |
| } |
| |
| state->metadata_percent_check = CHECK_MINIMUM; |
| state->data_percent_check = CHECK_MINIMUM; |
| *user = state; |
| |
| log_info("Monitoring thin %s.", device); |
| |
| return 1; |
| bad: |
| log_error("Failed to monitor thin %s.", device); |
| |
| return 0; |
| } |
| |
| int unregister_device(const char *device, |
| const char *uuid __attribute__((unused)), |
| int major __attribute__((unused)), |
| int minor __attribute__((unused)), |
| void **user) |
| { |
| struct dso_state *state = *user; |
| |
| dmeventd_lvm2_exit_with_pool(state); |
| log_info("No longer monitoring thin %s.", device); |
| |
| return 1; |
| } |