| /* |
| * Copyright (C) 2003-2004 Sistina Software, Inc. All rights reserved. |
| * Copyright (C) 2004-2008 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| |
| #include "lib.h" |
| #include "metadata.h" |
| #include "segtype.h" |
| #include "text_export.h" |
| #include "config.h" |
| #include "activate.h" |
| #include "str_list.h" |
| |
| #define SEG_LOG_ERROR(t, p...) \ |
| log_error(t " segment %s of logical volume %s.", ## p, \ |
| dm_config_parent_name(sn), seg->lv->name), 0; |
| |
| static const char *_snap_target_name(const struct lv_segment *seg, |
| const struct lv_activate_opts *laopts) |
| { |
| if (!laopts->no_merging && (seg->status & MERGING)) |
| return "snapshot-merge"; |
| |
| return lvseg_name(seg); |
| } |
| |
| static int _snap_text_import(struct lv_segment *seg, const struct dm_config_node *sn, |
| struct dm_hash_table *pv_hash __attribute__((unused))) |
| { |
| uint32_t chunk_size; |
| struct logical_volume *org, *cow; |
| const char *org_name = NULL, *cow_name = NULL; |
| int merge = 0; |
| |
| if (!dm_config_get_uint32(sn, "chunk_size", &chunk_size)) { |
| log_error("Couldn't read chunk size for snapshot."); |
| return 0; |
| } |
| |
| if (dm_config_has_node(sn, "merging_store")) { |
| if (!(cow_name = dm_config_find_str(sn, "merging_store", NULL))) |
| return SEG_LOG_ERROR("Merging store must be a string in"); |
| merge = 1; |
| } |
| |
| if (dm_config_has_node(sn, "cow_store")) { |
| if (cow_name) |
| return SEG_LOG_ERROR("Both snapshot cow and merging storage were specified in"); |
| |
| if (!(cow_name = dm_config_find_str(sn, "cow_store", NULL))) |
| return SEG_LOG_ERROR("Cow store must be a string in"); |
| } |
| |
| if (!cow_name) |
| return SEG_LOG_ERROR("Snapshot cow storage not specified in"); |
| |
| if (!dm_config_has_node(sn, "origin")) |
| return SEG_LOG_ERROR("Snapshot origin not specified in"); |
| |
| if (!(org_name = dm_config_find_str(sn, "origin", NULL))) |
| return SEG_LOG_ERROR("Snapshot origin must be a string in"); |
| |
| if (!(cow = find_lv(seg->lv->vg, cow_name))) |
| return SEG_LOG_ERROR("Unknown logical volume %s specified for " |
| "snapshot cow store in", cow_name); |
| |
| if (!(org = find_lv(seg->lv->vg, org_name))) |
| return SEG_LOG_ERROR("Unknown logical volume %s specified for " |
| "snapshot origin in", org_name); |
| |
| init_snapshot_seg(seg, org, cow, chunk_size, merge); |
| |
| return 1; |
| } |
| |
| static int _snap_text_export(const struct lv_segment *seg, struct formatter *f) |
| { |
| outf(f, "chunk_size = %u", seg->chunk_size); |
| outf(f, "origin = \"%s\"", seg->origin->name); |
| |
| if (!(seg->status & MERGING)) |
| outf(f, "cow_store = \"%s\"", seg->cow->name); |
| else |
| outf(f, "merging_store = \"%s\"", seg->cow->name); |
| |
| return 1; |
| } |
| |
| #ifdef DEVMAPPER_SUPPORT |
| static int _snap_target_status_compatible(const char *type) |
| { |
| return (strcmp(type, "snapshot-merge") == 0); |
| } |
| |
| static int _snap_target_percent(void **target_state __attribute__((unused)), |
| dm_percent_t *percent, |
| struct dm_pool *mem __attribute__((unused)), |
| struct cmd_context *cmd __attribute__((unused)), |
| struct lv_segment *seg __attribute__((unused)), |
| char *params, uint64_t *total_numerator, |
| uint64_t *total_denominator) |
| { |
| struct dm_status_snapshot *s; |
| |
| if (!dm_get_status_snapshot(mem, params, &s)) |
| return_0; |
| |
| if (s->invalid) |
| *percent = DM_PERCENT_INVALID; |
| else if (s->merge_failed) |
| *percent = LVM_PERCENT_MERGE_FAILED; |
| else { |
| *total_numerator += s->used_sectors; |
| *total_denominator += s->total_sectors; |
| if (s->has_metadata_sectors && |
| s->used_sectors == s->metadata_sectors) |
| *percent = DM_PERCENT_0; |
| else if (s->used_sectors == s->total_sectors) |
| *percent = DM_PERCENT_100; |
| else |
| *percent = dm_make_percent(*total_numerator, *total_denominator); |
| } |
| |
| return 1; |
| } |
| |
| static int _snap_target_present(struct cmd_context *cmd, |
| const struct lv_segment *seg, |
| unsigned *attributes) |
| { |
| static int _snap_checked = 0; |
| static int _snap_merge_checked = 0; |
| static int _snap_present = 0; |
| static int _snap_merge_present = 0; |
| static unsigned _snap_attrs = 0; |
| uint32_t maj, min, patchlevel; |
| |
| if (!_snap_checked) { |
| _snap_checked = 1; |
| _snap_present = target_present(cmd, "snapshot", 1) && |
| target_present(cmd, "snapshot-origin", 0); |
| |
| if (_snap_present && |
| target_version("snapshot", &maj, &min, &patchlevel) && |
| (maj > 1 || |
| (maj == 1 && (min >= 12 || (min == 10 && patchlevel >= 2))))) |
| _snap_attrs |= SNAPSHOT_FEATURE_FIXED_LEAK; |
| else |
| log_very_verbose("Target snapshot may leak metadata."); |
| } |
| |
| if (attributes) |
| *attributes = _snap_attrs; |
| |
| /* TODO: test everything at once */ |
| if (seg && (seg->status & MERGING)) { |
| if (!_snap_merge_checked) { |
| _snap_merge_present = target_present(cmd, "snapshot-merge", 0); |
| _snap_merge_checked = 1; |
| } |
| return _snap_present && _snap_merge_present; |
| } |
| |
| return _snap_present; |
| } |
| |
| # ifdef DMEVENTD |
| |
| static const char *_get_snapshot_dso_path(struct cmd_context *cmd) |
| { |
| return get_monitor_dso_path(cmd, find_config_tree_str(cmd, dmeventd_snapshot_library_CFG, NULL)); |
| } |
| |
| /* FIXME Cache this */ |
| static int _target_registered(struct lv_segment *seg, int *pending) |
| { |
| return target_registered_with_dmeventd(seg->lv->vg->cmd, _get_snapshot_dso_path(seg->lv->vg->cmd), |
| seg->cow, pending); |
| } |
| |
| /* FIXME This gets run while suspended and performs banned operations. */ |
| static int _target_set_events(struct lv_segment *seg, int evmask, int set) |
| { |
| /* FIXME Make timeout (10) configurable */ |
| return target_register_events(seg->lv->vg->cmd, _get_snapshot_dso_path(seg->lv->vg->cmd), |
| seg->cow, evmask, set, 10); |
| } |
| |
| static int _target_register_events(struct lv_segment *seg, |
| int events) |
| { |
| return _target_set_events(seg, events, 1); |
| } |
| |
| static int _target_unregister_events(struct lv_segment *seg, |
| int events) |
| { |
| return _target_set_events(seg, events, 0); |
| } |
| |
| # endif /* DMEVENTD */ |
| |
| static int _snap_modules_needed(struct dm_pool *mem, |
| const struct lv_segment *seg __attribute__((unused)), |
| struct dm_list *modules) |
| { |
| if (!str_list_add(mem, modules, "snapshot")) { |
| log_error("snapshot string list allocation failed"); |
| return 0; |
| } |
| |
| return 1; |
| } |
| #endif /* DEVMAPPER_SUPPORT */ |
| |
| static void _snap_destroy(struct segment_type *segtype) |
| { |
| dm_free(segtype); |
| } |
| |
| static struct segtype_handler _snapshot_ops = { |
| .target_name = _snap_target_name, |
| .text_import = _snap_text_import, |
| .text_export = _snap_text_export, |
| #ifdef DEVMAPPER_SUPPORT |
| .target_status_compatible = _snap_target_status_compatible, |
| .target_percent = _snap_target_percent, |
| .target_present = _snap_target_present, |
| .modules_needed = _snap_modules_needed, |
| # ifdef DMEVENTD |
| .target_monitored = _target_registered, |
| .target_monitor_events = _target_register_events, |
| .target_unmonitor_events = _target_unregister_events, |
| # endif /* DMEVENTD */ |
| #endif |
| .destroy = _snap_destroy, |
| }; |
| |
| #ifdef SNAPSHOT_INTERNAL |
| struct segment_type *init_snapshot_segtype(struct cmd_context *cmd) |
| #else /* Shared */ |
| struct segment_type *init_segtype(struct cmd_context *cmd); |
| struct segment_type *init_segtype(struct cmd_context *cmd) |
| #endif |
| { |
| struct segment_type *segtype = dm_zalloc(sizeof(*segtype)); |
| |
| if (!segtype) |
| return_NULL; |
| |
| segtype->ops = &_snapshot_ops; |
| segtype->name = "snapshot"; |
| segtype->flags = SEG_SNAPSHOT | SEG_CANNOT_BE_ZEROED | SEG_ONLY_EXCLUSIVE; |
| |
| #ifdef DEVMAPPER_SUPPORT |
| # ifdef DMEVENTD |
| if (_get_snapshot_dso_path(cmd)) |
| segtype->flags |= SEG_MONITORED; |
| # endif /* DMEVENTD */ |
| #endif |
| log_very_verbose("Initialised segtype: %s", segtype->name); |
| |
| return segtype; |
| } |