| /* |
| * Copyright (C) 2007-2015 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" |
| #include "dmeventd_lvm.h" |
| #include "libdevmapper-event.h" |
| |
| #include <sys/wait.h> |
| #include <stdarg.h> |
| #include <pthread.h> |
| |
| /* First warning when snapshot is 80% full. */ |
| #define WARNING_THRESH (DM_PERCENT_1 * 80) |
| /* Run a check every 5%. */ |
| #define CHECK_STEP (DM_PERCENT_1 * 5) |
| /* Do not bother checking snapshots less than 50% full. */ |
| #define CHECK_MINIMUM (DM_PERCENT_1 * 50) |
| |
| #define UMOUNT_COMMAND "/bin/umount" |
| |
| struct dso_state { |
| struct dm_pool *mem; |
| dm_percent_t percent_check; |
| uint64_t known_size; |
| char cmd_lvextend[512]; |
| }; |
| |
| DM_EVENT_LOG_FN("snap") |
| |
| 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 */ |
| } |
| |
| static int _extend(const char *cmd) |
| { |
| log_debug("Extending snapshot via %s.", cmd); |
| return dmeventd_lvm2_run_with_lock(cmd); |
| } |
| |
| #ifdef SNAPSHOT_REMOVE |
| /* Remove invalid snapshot from dm-table */ |
| /* Experimental for now and not used by default */ |
| static int _remove(const char *uuid) |
| { |
| int r = 1; |
| uint32_t cookie = 0; |
| struct dm_task *dmt; |
| |
| if (!(dmt = dm_task_create(DM_DEVICE_REMOVE))) |
| return 0; |
| |
| if (!dm_task_set_uuid(dmt, uuid)) { |
| r = 0; |
| goto_out; |
| } |
| |
| dm_task_retry_remove(dmt); |
| |
| if (!dm_task_set_cookie(dmt, &cookie, 0)) { |
| r = 0; |
| goto_out; |
| } |
| |
| if (!dm_task_run(dmt)) { |
| r = 0; |
| goto_out; |
| } |
| out: |
| dm_task_destroy(dmt); |
| |
| return r; |
| } |
| #endif /* SNAPSHOT_REMOVE */ |
| |
| static void _umount(const char *device, int major, int minor) |
| { |
| FILE *mounts; |
| char buffer[4096]; |
| char *words[3]; |
| struct stat st; |
| const char procmounts[] = "/proc/mounts"; |
| |
| if (!(mounts = fopen(procmounts, "r"))) { |
| log_sys_error("fopen", procmounts); |
| log_error("Not umounting %s.", device); |
| return; |
| } |
| |
| while (!feof(mounts)) { |
| /* read a line of /proc/mounts */ |
| if (!fgets(buffer, sizeof(buffer), mounts)) |
| break; /* eof, likely */ |
| |
| /* words[0] is the mount point and words[1] is the device path */ |
| if (dm_split_words(buffer, 3, 0, words) < 2) |
| continue; |
| |
| /* find the major/minor of the device */ |
| if (stat(words[0], &st)) |
| continue; /* can't stat, skip this one */ |
| |
| if (S_ISBLK(st.st_mode) && |
| (int) major(st.st_rdev) == major && |
| (int) minor(st.st_rdev) == minor) { |
| log_error("Unmounting invalid snapshot %s from %s.", device, words[1]); |
| if (!_run(UMOUNT_COMMAND, "-fl", words[1], NULL)) |
| log_error("Failed to umount snapshot %s from %s: %s.", |
| device, words[1], strerror(errno)); |
| } |
| } |
| |
| if (fclose(mounts)) |
| log_sys_error("close", procmounts); |
| } |
| |
| void process_event(struct dm_task *dmt, |
| enum dm_event_mask event __attribute__((unused)), |
| void **user) |
| { |
| struct dso_state *state = *user; |
| void *next = NULL; |
| uint64_t start, length; |
| char *target_type = NULL; |
| char *params; |
| struct dm_status_snapshot *status = NULL; |
| const char *device = dm_task_get_name(dmt); |
| int percent; |
| struct dm_info info; |
| |
| /* No longer monitoring, waiting for remove */ |
| if (!state->percent_check) |
| return; |
| |
| dm_get_next_target(dmt, next, &start, &length, &target_type, ¶ms); |
| if (!target_type || strcmp(target_type, "snapshot")) { |
| log_error("Target %s is not snapshot.", target_type); |
| return; |
| } |
| |
| if (!dm_get_status_snapshot(state->mem, params, &status)) { |
| log_error("Cannot parse snapshot %s state: %s.", device, params); |
| return; |
| } |
| |
| /* |
| * If the snapshot has been invalidated or we failed to parse |
| * the status string. Report the full status string to syslog. |
| */ |
| if (status->invalid || status->overflow || !status->total_sectors) { |
| log_warn("WARNING: Snapshot %s changed state to: %s and should be removed.", |
| device, params); |
| state->percent_check = 0; |
| if (dm_task_get_info(dmt, &info)) |
| _umount(device, info.major, info.minor); |
| #ifdef SNAPSHOT_REMOVE |
| /* Maybe configurable ? */ |
| _remove(dm_task_get_uuid(dmt)); |
| #endif |
| pthread_kill(pthread_self(), SIGALRM); |
| goto out; |
| } |
| |
| if (length <= (status->used_sectors - status->metadata_sectors)) { |
| /* TODO eventually recognize earlier when room is enough */ |
| log_info("Dropping monitoring of fully provisioned snapshot %s.", |
| device); |
| pthread_kill(pthread_self(), SIGALRM); |
| goto out; |
| } |
| |
| /* Snapshot size had changed. Clear the threshold. */ |
| if (state->known_size != status->total_sectors) { |
| state->percent_check = CHECK_MINIMUM; |
| state->known_size = status->total_sectors; |
| } |
| |
| percent = dm_make_percent(status->used_sectors, status->total_sectors); |
| if (percent >= state->percent_check) { |
| /* Usage has raised more than CHECK_STEP since the last |
| time. Run actions. */ |
| state->percent_check = (percent / CHECK_STEP) * CHECK_STEP + CHECK_STEP; |
| |
| if (percent >= WARNING_THRESH) /* Print a warning to syslog. */ |
| log_warn("WARNING: Snapshot %s is now %.2f%% full.", |
| device, dm_percent_to_float(percent)); |
| |
| /* Try to extend the snapshot, in accord with user-set policies */ |
| if (!_extend(state->cmd_lvextend)) |
| log_error("Failed to extend snapshot %s.", device); |
| } |
| out: |
| dm_pool_free(state->mem, status); |
| } |
| |
| 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("snapshot_state", state)) |
| goto_bad; |
| |
| if (!dmeventd_lvm2_command(state->mem, state->cmd_lvextend, |
| sizeof(state->cmd_lvextend), |
| "lvextend --use-policies", device)) { |
| dmeventd_lvm2_exit_with_pool(state); |
| goto_bad; |
| } |
| |
| state->percent_check = CHECK_MINIMUM; |
| *user = state; |
| |
| log_info("Monitoring snapshot %s.", device); |
| |
| return 1; |
| bad: |
| log_error("Failed to monitor snapshot %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 snapshot %s.", device); |
| |
| return 1; |
| } |