/* * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved. * Copyright (C) 2004-2017 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/misc/lib.h" #include "lib/metadata/metadata.h" #include "lib/display/display.h" #include "lib/activate/activate.h" #include "lib/commands/toolcontext.h" #include "lib/metadata/segtype.h" #include "lib/datastruct/str_list.h" #include "lib/locking/lvmlockd.h" #include #include static struct utsname _utsname; static int _utsinit = 0; int lv_is_historical(const struct logical_volume *lv) { return lv->this_glv && lv->this_glv->is_historical; } static struct dm_list *_format_pvsegs(struct dm_pool *mem, const struct lv_segment *seg, int range_format, int metadata_areas_only, int mark_hidden) { unsigned int s; const char *name = NULL; uint32_t extent = 0; uint32_t seg_len = 0; char extent_str[32]; struct logical_volume *lv; int visible = 1; char *list_item; size_t list_item_len; struct dm_list *result = NULL; if (!(result = str_list_create(mem))) { log_error("_format_pvsegs: str_list_create failed"); goto bad; } if (metadata_areas_only && (!seg_is_raid_with_meta(seg) || !seg->meta_areas || lv_is_raid_metadata(seg->lv) || lv_is_raid_image(seg->lv))) goto out; for (s = 0; s < seg->area_count; s++) { switch (metadata_areas_only ? seg_metatype(seg, s) : seg_type(seg, s)) { case AREA_LV: lv = metadata_areas_only ? seg_metalv(seg, s) : seg_lv(seg, s); seg_len = metadata_areas_only ? seg_metalv(seg, s)->le_count : seg_lv(seg, s)->le_count; visible = lv_is_visible(lv); name = lv->name; extent = metadata_areas_only ? seg_le(seg, s) : 0; break; case AREA_PV: /* Raid metadata never uses PVs directly */ if (metadata_areas_only) continue; name = dev_name(seg_dev(seg, s)); extent = seg_pe(seg, s); seg_len = seg->area_len; break; case AREA_UNASSIGNED: name = "unassigned"; extent = 0; seg_len = 0; break; default: log_error(INTERNAL_ERROR "Unknown area segtype."); goto bad; } list_item_len = strlen(name); if (!visible && mark_hidden) /* +2 for [ ] */ list_item_len += 2; if (range_format) { if (dm_snprintf(extent_str, sizeof(extent_str), ":%" PRIu32 "-%" PRIu32, extent, extent + seg_len - 1) < 0) { log_error("_format_pvsegs: extent range dm_snprintf failed."); goto bad; } } else { if (dm_snprintf(extent_str, sizeof(extent_str), "(%" PRIu32 ")", extent) < 0) { log_error("_format_pvsegs: extent number dm_snprintf failed."); goto bad; } } list_item_len += strlen(extent_str); /* trialing 0 */ list_item_len += 1; if (!(list_item = dm_pool_zalloc(mem, list_item_len))) { log_error("_format_pvsegs: list item dm_pool_zalloc failed"); goto bad; } if (dm_snprintf(list_item, list_item_len, "%s%s%s%s", (!visible && mark_hidden) ? "[" : "", name, (!visible && mark_hidden) ? "]" : "", extent_str) < 0) { log_error("_format_pvsegs: list item dm_snprintf failed"); goto bad; } if (!str_list_add_no_dup_check(mem, result, list_item)) { log_error("_format_pvsegs: failed to add item to list"); goto bad; } } out: return result; bad: dm_pool_free(mem, result); return NULL; } struct dm_list *lvseg_devices(struct dm_pool *mem, const struct lv_segment *seg) { return _format_pvsegs(mem, seg, 0, 0, 0); } char *lvseg_devices_str(struct dm_pool *mem, const struct lv_segment *seg) { struct dm_list *list; if (!(list = lvseg_devices(mem, seg))) return_NULL; return str_list_to_str(mem, list, ","); } struct dm_list *lvseg_metadata_devices(struct dm_pool *mem, const struct lv_segment *seg) { return _format_pvsegs(mem, seg, 0, 1, 0); } char *lvseg_metadata_devices_str(struct dm_pool *mem, const struct lv_segment *seg) { struct dm_list *list; if (!(list = lvseg_devices(mem, seg))) return_NULL; return str_list_to_str(mem, list, ","); } struct dm_list *lvseg_seg_pe_ranges(struct dm_pool *mem, const struct lv_segment *seg) { return _format_pvsegs(mem, seg, 1, 0, 0); } char *lvseg_seg_pe_ranges_str(struct dm_pool *mem, const struct lv_segment *seg) { struct dm_list *list; if (!(list = lvseg_seg_pe_ranges(mem, seg))) return_NULL; return str_list_to_str(mem, list, " "); } struct dm_list *lvseg_seg_le_ranges(struct dm_pool *mem, const struct lv_segment *seg) { return _format_pvsegs(mem, seg, 1, 0, seg->lv->vg->cmd->report_mark_hidden_devices); } char *lvseg_seg_le_ranges_str(struct dm_pool *mem, const struct lv_segment *seg) { struct dm_list *list; if (!(list = lvseg_seg_pe_ranges(mem, seg))) return_NULL; return str_list_to_str(mem, list, seg->lv->vg->cmd->report_list_item_separator); } struct dm_list *lvseg_seg_metadata_le_ranges(struct dm_pool *mem, const struct lv_segment *seg) { return _format_pvsegs(mem, seg, 1, 1, seg->lv->vg->cmd->report_mark_hidden_devices); } char *lvseg_seg_metadata_le_ranges_str(struct dm_pool *mem, const struct lv_segment *seg) { struct dm_list *list; if (!(list = lvseg_seg_metadata_le_ranges(mem, seg))) return_NULL; return str_list_to_str(mem, list, seg->lv->vg->cmd->report_list_item_separator); } char *lvseg_tags_dup(const struct lv_segment *seg) { return tags_format_and_copy(seg->lv->vg->vgmem, &seg->tags); } char *lvseg_segtype_dup(struct dm_pool *mem, const struct lv_segment *seg) { return dm_pool_strdup(mem, lvseg_name(seg)); } char *lvseg_discards_dup(struct dm_pool *mem, const struct lv_segment *seg) { if (lv_is_thin_pool(seg->lv)) return dm_pool_strdup(mem, get_pool_discards_name(seg->discards)); log_error("Cannot query non thin-pool segment of LV %s for discards property.", display_lvname(seg->lv)); return NULL; } char *lvseg_kernel_discards_dup_with_info_and_seg_status(struct dm_pool *mem, const struct lv_with_info_and_seg_status *lvdm) { const char *s = ""; char *ret; thin_discards_t d; if (lvdm->seg_status.type == SEG_STATUS_THIN_POOL) { switch (lvdm->seg_status.thin_pool->discards) { case DM_THIN_DISCARDS_IGNORE: d = THIN_DISCARDS_IGNORE; break; case DM_THIN_DISCARDS_NO_PASSDOWN: d = THIN_DISCARDS_NO_PASSDOWN; break; case DM_THIN_DISCARDS_PASSDOWN: d = THIN_DISCARDS_PASSDOWN; break; default: log_error("Kernel reports unknown discards status %u.", lvdm->seg_status.thin_pool->discards); return 0; } s = get_pool_discards_name(d); } else if (lvdm->seg_status.type == SEG_STATUS_CACHE) { if (lvdm->seg_status.cache->feature_flags & DM_CACHE_FEATURE_NO_DISCARD_PASSDOWN) { s = "nopassdown"; } } if (!(ret = dm_pool_strdup(mem, s))) { log_error("lvseg_kernel_discards_dup_with_info_and_seg_status: dm_pool_strdup failed."); return NULL; } return ret; } char *lvseg_kernel_discards_dup(struct dm_pool *mem, const struct lv_segment *seg) { char *ret = NULL; struct lv_with_info_and_seg_status status = { .seg_status.type = SEG_STATUS_NONE }; if (!lv_is_thin_pool(seg->lv)) return NULL; if (!(status.seg_status.mem = dm_pool_create("reporter_pool", 1024))) return_NULL; if (!(status.info_ok = lv_info_with_seg_status(seg->lv->vg->cmd, seg, &status, 0, 0))) goto_bad; if (!(ret = lvseg_kernel_discards_dup_with_info_and_seg_status(mem, &status))) stack; bad: dm_pool_destroy(status.seg_status.mem); return ret; } char *lvseg_cachemode_dup(struct dm_pool *mem, const struct lv_segment *seg) { const char *name = get_cache_mode_name(seg); if (!name) return_NULL; return dm_pool_strdup(mem, name); } #ifdef DMEVENTD # include "daemons/dmeventd/libdevmapper-event.h" #endif char *lvseg_monitor_dup(struct dm_pool *mem, const struct lv_segment *seg) { const char *s = ""; #ifdef DMEVENTD struct lvinfo info; int pending = 0, monitored = 0; struct lv_segment *segm = (struct lv_segment *) seg; if (lv_is_cow(seg->lv) && (!lv_is_merging_cow(seg->lv) || lv_has_target_type(seg->lv->vg->cmd->mem, seg->lv, NULL, TARGET_NAME_SNAPSHOT))) segm = first_seg(seg->lv->snapshot->lv); // log_debug("Query LV:%s mon:%s segm:%s tgtm:%p segmon:%d statusm:%d", seg->lv->name, segm->lv->name, segm->segtype->name, segm->segtype->ops->target_monitored, seg_monitored(segm), (int)(segm->status & PVMOVE)); if (!segm->segtype->ops || !segm->segtype->ops->target_monitored) /* Nothing to do, monitoring not supported */; else if (dmeventd_monitor_mode() != 1) s = "not enabled"; else if (lv_is_cow_covering_origin(seg->lv)) /* Nothing to do, snapshot already covers origin */; else if (!seg_monitored(segm) || (segm->status & PVMOVE)) s = "not monitored"; else if (lv_info(seg->lv->vg->cmd, seg->lv, 1, &info, 0, 0) && info.exists) { if (segm->segtype->ops->target_monitored(segm, &pending, &monitored)) { if (pending) s = "pending"; else s = (monitored) ? "monitored" : "not monitored"; } else s = "not monitored"; } // else log_debug("Not active"); #endif return dm_pool_strdup(mem, s); } uint64_t lvseg_chunksize(const struct lv_segment *seg) { uint64_t size; if (lv_is_cow(seg->lv)) size = (uint64_t) find_snapshot(seg->lv)->chunk_size; else if (seg_is_cache(seg) && lv_is_cache_vol(seg->pool_lv)) size = (uint64_t) seg->chunk_size; else if (seg_is_pool(seg)) size = (uint64_t) seg->chunk_size; else if (seg_is_cache(seg)) return lvseg_chunksize(first_seg(seg->pool_lv)); else size = UINT64_C(0); return size; } const char *lvseg_name(const struct lv_segment *seg) { /* Support even segtypes without 'ops' */ if (seg->segtype->ops && seg->segtype->ops->name) return seg->segtype->ops->name(seg); return seg->segtype->name; } uint64_t lvseg_start(const struct lv_segment *seg) { return (uint64_t) seg->le * seg->lv->vg->extent_size; } uint64_t lvseg_size(const struct lv_segment *seg) { return (uint64_t) seg->len * seg->lv->vg->extent_size; } dm_percent_t lvseg_percent_with_info_and_seg_status(const struct lv_with_info_and_seg_status *lvdm, percent_get_t type) { dm_percent_t p; uint64_t csize; const struct lv_segment *seg; const struct lv_seg_status *s = &lvdm->seg_status; /* * TODO: * Later move to segment methods, instead of using single place. * Also handle logic for mirror segments and it total_* summing * Essentially rework _target_percent API for segtype. */ switch (s->type) { case SEG_STATUS_INTEGRITY: if (type != PERCENT_GET_DIRTY) p = DM_PERCENT_INVALID; else if (!s->integrity->recalc_sector) p = DM_PERCENT_INVALID; else if (s->integrity->recalc_sector == s->integrity->provided_data_sectors) p = DM_PERCENT_100; else p = dm_make_percent(s->integrity->recalc_sector, s->integrity->provided_data_sectors); break; case SEG_STATUS_CACHE: if (s->cache->fail || s->cache->error) p = DM_PERCENT_INVALID; else { switch (type) { case PERCENT_GET_DIRTY: p = (s->cache->used_blocks) ? dm_make_percent(s->cache->dirty_blocks, s->cache->used_blocks) : DM_PERCENT_0; break; case PERCENT_GET_METADATA: p = dm_make_percent(s->cache->metadata_used_blocks, s->cache->metadata_total_blocks); break; default: p = dm_make_percent(s->cache->used_blocks, s->cache->total_blocks); } } break; case SEG_STATUS_WRITECACHE: if (type != PERCENT_GET_DATA) p = DM_PERCENT_INVALID; else { uint64_t used = s->writecache->total_blocks - s->writecache->free_blocks; p = dm_make_percent(used, s->writecache->total_blocks); } break; case SEG_STATUS_RAID: switch (type) { case PERCENT_GET_DIRTY: p = dm_make_percent(s->raid->insync_regions, s->raid->total_regions); break; default: p = DM_PERCENT_INVALID; } break; case SEG_STATUS_SNAPSHOT: if (s->snapshot->merge_failed) p = DM_PERCENT_INVALID; else if (s->snapshot->invalid) p = DM_PERCENT_100; /* Shown as 100% full */ else if (s->snapshot->has_metadata_sectors && (s->snapshot->used_sectors == s->snapshot->metadata_sectors)) p = DM_PERCENT_0; else p = dm_make_percent(s->snapshot->used_sectors, s->snapshot->total_sectors); break; case SEG_STATUS_THIN_POOL: if (s->thin_pool->fail || s->thin_pool->error) p = DM_PERCENT_INVALID; else if (type == PERCENT_GET_METADATA) p = dm_make_percent(s->thin_pool->used_metadata_blocks, s->thin_pool->total_metadata_blocks); else p = dm_make_percent(s->thin_pool->used_data_blocks, s->thin_pool->total_data_blocks); break; case SEG_STATUS_THIN: if (s->thin->fail || (type != PERCENT_GET_DATA)) /* TODO: expose highest mapped sector */ p = DM_PERCENT_INVALID; else { seg = lvdm->seg_status.seg; /* Pool allocates whole chunk so round-up to nearest one */ csize = first_seg(seg->pool_lv)->chunk_size; csize = ((seg->lv->size + csize - 1) / csize) * csize; if (s->thin->mapped_sectors <= csize) p = dm_make_percent(s->thin->mapped_sectors, csize); else { log_warn("WARNING: Thin volume %s maps %s while the size is only %s.", display_lvname(seg->lv), display_size(seg->lv->vg->cmd, s->thin->mapped_sectors), display_size(seg->lv->vg->cmd, csize)); /* Don't show nonsense numbers like i.e. 1000% full */ p = DM_PERCENT_100; } } break; case SEG_STATUS_VDO_POOL: if (seg_is_vdo_pool(lvdm->seg_status.seg)) p = s->vdo_pool.usage; else p = s->vdo_pool.data_usage; break; default: p = DM_PERCENT_INVALID; } return p; } uint32_t lv_kernel_read_ahead(const struct logical_volume *lv) { struct lvinfo info; if (!lv_info(lv->vg->cmd, lv, 0, &info, 0, 1) || !info.exists) return UINT32_MAX; return info.read_ahead; } struct pv_and_int { struct physical_volume *pv; int *i; }; static int _lv_is_on_pv(struct logical_volume *lv, void *data) { int *is_on_pv = ((struct pv_and_int *)data)->i; struct physical_volume *pv = ((struct pv_and_int *)data)->pv; uint32_t s; struct physical_volume *pv2; struct lv_segment *seg; if (!lv || !(first_seg(lv))) return_0; /* * If the LV has already been found to be on the PV, then * we don't need to continue checking - just return. */ if (*is_on_pv) return 1; dm_list_iterate_items(seg, &lv->segments) { for (s = 0; s < seg->area_count; s++) { if (seg_type(seg, s) != AREA_PV) continue; pv2 = seg_pv(seg, s); if (id_equal(&pv->id, &pv2->id)) { *is_on_pv = 1; return 1; } if (pv->dev && pv2->dev && (pv->dev->dev == pv2->dev->dev)) { *is_on_pv = 1; return 1; } } } return 1; } /* * lv_is_on_pv * @lv: * @pv: * * If any of the component devices of the LV are on the given PV, 1 * is returned; otherwise 0. For example if one of the images of a RAID * (or its metadata device) is on the PV, 1 would be returned for the * top-level LV. * If you wish to check the images themselves, you should pass them. * * Returns: 1 if LV (or part of LV) is on PV, 0 otherwise */ int lv_is_on_pv(struct logical_volume *lv, struct physical_volume *pv) { int is_on_pv = 0; struct pv_and_int context = { pv, &is_on_pv }; if (!_lv_is_on_pv(lv, &context) || !for_each_sub_lv(lv, _lv_is_on_pv, &context)) /* Failure only happens if bad arguments are passed */ log_error(INTERNAL_ERROR "for_each_sub_lv failure."); log_debug_metadata("%s is %son %s", lv->name, is_on_pv ? "" : "not ", pv_dev_name(pv)); return is_on_pv; } /* * lv_is_on_pvs * @lv * @pvs * * Returns 1 if the LV (or part of the LV) is on any of the pvs * in the list, 0 otherwise. */ int lv_is_on_pvs(struct logical_volume *lv, struct dm_list *pvs) { struct pv_list *pvl; dm_list_iterate_items(pvl, pvs) if (lv_is_on_pv(lv, pvl->pv)) return 1; return 0; } struct logical_volume *lv_origin_lv(const struct logical_volume *lv) { struct logical_volume *origin = NULL; if (lv_is_cow(lv)) origin = origin_from_cow(lv); else if (lv_is_cache(lv) && !lv_is_pending_delete(lv)) origin = seg_lv(first_seg(lv), 0); else if (lv_is_thin_volume(lv) && first_seg(lv)->origin) origin = first_seg(lv)->origin; else if (lv_is_thin_volume(lv) && first_seg(lv)->external_lv) origin = first_seg(lv)->external_lv; else if (lv_is_writecache(lv) && first_seg(lv)->origin) origin = first_seg(lv)->origin; else if (lv_is_integrity(lv) && first_seg(lv)->origin) origin = first_seg(lv)->origin; return origin; } static char *_do_lv_origin_dup(struct dm_pool *mem, const struct logical_volume *lv, int uuid) { struct logical_volume *origin_lv = lv_origin_lv(lv); if (!origin_lv) return NULL; if (uuid) return lv_uuid_dup(mem, origin_lv); return lv_name_dup(mem, origin_lv); } char *lv_origin_dup(struct dm_pool *mem, const struct logical_volume *lv) { return _do_lv_origin_dup(mem, lv, 0); } char *lv_origin_uuid_dup(struct dm_pool *mem, const struct logical_volume *lv) { return _do_lv_origin_dup(mem, lv, 1); } char *lv_name_dup(struct dm_pool *mem, const struct logical_volume *lv) { return dm_pool_strdup(mem, lv->name); } char *lv_fullname_dup(struct dm_pool *mem, const struct logical_volume *lv) { char lvfullname[NAME_LEN * 2 + 2]; if (dm_snprintf(lvfullname, sizeof(lvfullname), "%s/%s", lv->vg->name, lv->name) < 0) { log_error("lvfullname snprintf failed"); return NULL; } return dm_pool_strdup(mem, lvfullname); } struct logical_volume *lv_parent(const struct logical_volume *lv) { struct logical_volume *parent_lv = NULL; struct lv_segment *seg; if (lv_is_visible(lv)) ; else if ((lv_is_mirror_image(lv) || lv_is_mirror_log(lv)) || (lv_is_raid_image(lv) || lv_is_raid_metadata(lv)) || (lv_is_cache_pool_data(lv) || lv_is_cache_pool_metadata(lv)) || (lv_is_thin_pool_data(lv) || lv_is_thin_pool_metadata(lv))) { if (!(seg = get_only_segment_using_this_lv(lv))) stack; else parent_lv = seg->lv; } return parent_lv; } char *lv_parent_dup(struct dm_pool *mem, const struct logical_volume *lv) { struct logical_volume *parent_lv = lv_parent(lv); return dm_pool_strdup(mem, parent_lv ? parent_lv->name : ""); } char *lv_modules_dup(struct dm_pool *mem, const struct logical_volume *lv) { struct dm_list *modules; if (!(modules = str_list_create(mem))) { log_error("modules str_list allocation failed"); return NULL; } if (!list_lv_modules(mem, lv, modules)) return_NULL; return tags_format_and_copy(mem, modules); } struct logical_volume *lv_mirror_log_lv(const struct logical_volume *lv) { struct lv_segment *seg; dm_list_iterate_items(seg, &lv->segments) { if (seg_is_mirrored(seg) && seg->log_lv) return seg->log_lv; } return NULL; } static char *_do_lv_mirror_log_dup(struct dm_pool *mem, const struct logical_volume *lv, int uuid) { struct logical_volume *mirror_log_lv = lv_mirror_log_lv(lv); if (!mirror_log_lv) return NULL; if (uuid) return lv_uuid_dup(mem, mirror_log_lv); return lv_name_dup(mem, mirror_log_lv); } char *lv_mirror_log_dup(struct dm_pool *mem, const struct logical_volume *lv) { return _do_lv_mirror_log_dup(mem, lv, 0); } char *lv_mirror_log_uuid_dup(struct dm_pool *mem, const struct logical_volume *lv) { return _do_lv_mirror_log_dup(mem, lv, 1); } struct logical_volume *lv_pool_lv(const struct logical_volume *lv) { if (lv_is_thin_volume(lv) || lv_is_cache(lv)) return first_seg(lv)->pool_lv; if (lv_is_vdo(lv)) return seg_lv(first_seg(lv), 0); if (lv_is_writecache(lv)) return first_seg(lv)->writecache; return NULL; } static char *_do_lv_pool_lv_dup(struct dm_pool *mem, const struct logical_volume *lv, int uuid) { struct logical_volume *pool_lv = lv_pool_lv(lv); if (!pool_lv) return NULL; if (uuid) return lv_uuid_dup(mem, pool_lv); return lv_name_dup(mem, pool_lv); } char *lv_pool_lv_dup(struct dm_pool *mem, const struct logical_volume *lv) { return _do_lv_pool_lv_dup(mem, lv, 0); } char *lv_pool_lv_uuid_dup(struct dm_pool *mem, const struct logical_volume *lv) { return _do_lv_pool_lv_dup(mem, lv, 1); } struct logical_volume *lv_data_lv(const struct logical_volume *lv) { struct lv_segment *seg = (lv_is_cache_pool(lv) || lv_is_thin_pool(lv) || lv_is_vdo_pool(lv)) ? first_seg(lv) : NULL; struct logical_volume *data_lv = seg ? seg_lv(seg, 0) : NULL; return data_lv; } static char *_do_lv_data_lv_dup(struct dm_pool *mem, const struct logical_volume *lv, int uuid) { struct logical_volume *data_lv = lv_data_lv(lv); if (!data_lv) return NULL; if (uuid) return lv_uuid_dup(mem, data_lv); return lv_name_dup(mem, data_lv); } char *lv_data_lv_dup(struct dm_pool *mem, const struct logical_volume *lv) { return _do_lv_data_lv_dup(mem, lv, 0); } char *lv_data_lv_uuid_dup(struct dm_pool *mem, const struct logical_volume *lv) { return _do_lv_data_lv_dup(mem, lv, 1); } struct logical_volume *lv_metadata_lv(const struct logical_volume *lv) { struct lv_segment *seg = (lv_is_thin_pool(lv) || lv_is_cache_pool(lv)) ? first_seg(lv) : NULL; struct logical_volume *metadata_lv = seg ? seg->metadata_lv : NULL; return metadata_lv; } static char *_do_lv_metadata_lv_dup(struct dm_pool *mem, const struct logical_volume *lv, int uuid) { struct logical_volume *metadata_lv = lv_metadata_lv(lv); if (!metadata_lv) return NULL; if (uuid) return lv_uuid_dup(mem, metadata_lv); return lv_name_dup(mem, metadata_lv); } char *lv_metadata_lv_dup(struct dm_pool *mem, const struct logical_volume *lv) { return _do_lv_metadata_lv_dup(mem, lv, 0); } char *lv_metadata_lv_uuid_dup(struct dm_pool *mem, const struct logical_volume *lv) { return _do_lv_metadata_lv_dup(mem, lv, 1); } const char *lv_layer(const struct logical_volume *lv) { if (lv_is_thin_pool(lv)) return "tpool"; if (lv_is_vdo_pool(lv)) return "vpool"; if (lv_is_origin(lv) || lv_is_external_origin(lv) || lv_is_writecache_origin(lv)) return "real"; return NULL; } int lv_kernel_minor(const struct logical_volume *lv) { struct lvinfo info; if (lv_info(lv->vg->cmd, lv, 0, &info, 0, 0) && info.exists) return info.minor; return -1; } int lv_kernel_major(const struct logical_volume *lv) { struct lvinfo info; if (lv_info(lv->vg->cmd, lv, 0, &info, 0, 0) && info.exists) return info.major; return -1; } struct logical_volume *lv_convert_lv(const struct logical_volume *lv) { struct lv_segment *seg; if (lv_is_converting(lv) || lv_is_mirrored(lv)) { seg = first_seg(lv); /* Temporary mirror is always area_num == 0 */ if (seg_type(seg, 0) == AREA_LV && is_temporary_mirror_layer(seg_lv(seg, 0))) return seg_lv(seg, 0); } return NULL; } static char *_do_lv_convert_lv_dup(struct dm_pool *mem, const struct logical_volume *lv, int uuid) { struct logical_volume *convert_lv = lv_convert_lv(lv); if (!convert_lv) return NULL; if (uuid) return lv_uuid_dup(mem, convert_lv); return lv_name_dup(mem, convert_lv); } char *lv_convert_lv_dup(struct dm_pool *mem, const struct logical_volume *lv) { return _do_lv_convert_lv_dup(mem, lv, 0); } char *lv_convert_lv_uuid_dup(struct dm_pool *mem, const struct logical_volume *lv) { return _do_lv_convert_lv_dup(mem, lv, 1); } static char *_do_lv_move_pv_dup(struct dm_pool *mem, const struct logical_volume *lv, int uuid) { struct logical_volume *mimage0_lv; struct lv_segment *seg; struct pv_segment *pvseg; dm_list_iterate_items(seg, &lv->segments) { if (seg->status & PVMOVE) { if (seg_type(seg, 0) == AREA_LV) { /* atomic pvmove */ mimage0_lv = seg_lv(seg, 0); if (!lv_is_mirror_image(mimage0_lv)) { log_error(INTERNAL_ERROR "Bad pvmove structure"); return NULL; } pvseg = seg_pvseg(first_seg(mimage0_lv), 0); } else /* Segment pvmove */ pvseg = seg_pvseg(seg, 0); if (uuid) return pv_uuid_dup(mem, pvseg->pv); return pv_name_dup(mem, pvseg->pv); } } return NULL; } char *lv_move_pv_dup(struct dm_pool *mem, const struct logical_volume *lv) { return _do_lv_move_pv_dup(mem, lv, 0); } char *lv_move_pv_uuid_dup(struct dm_pool *mem, const struct logical_volume *lv) { return _do_lv_move_pv_dup(mem, lv, 1); } uint64_t lv_origin_size(const struct logical_volume *lv) { struct lv_segment *seg; if (lv_is_cow(lv)) return find_snapshot(lv)->lv->size; if (lv_is_thin_volume(lv) && (seg = first_seg(lv)) && seg->external_lv) return seg->external_lv->size; if (lv_is_origin(lv)) return lv->size; return 0; } uint64_t lv_metadata_size(const struct logical_volume *lv) { struct lv_segment *seg; if (!(seg = first_seg(lv))) return 0; if (seg_is_cache(seg) && lv_is_cache_vol(seg->pool_lv)) return seg->metadata_len; if (lv_is_thin_pool(lv) || lv_is_cache_pool(lv)) return seg->metadata_lv->size; return 0; } char *lv_path_dup(struct dm_pool *mem, const struct logical_volume *lv) { char *repstr; size_t len; /* Only for visible devices that get a link from /dev/vg */ if (!*lv->vg->name || !lv_is_visible(lv) || lv_is_thin_pool(lv)) return dm_pool_strdup(mem, ""); len = strlen(lv->vg->cmd->dev_dir) + strlen(lv->vg->name) + strlen(lv->name) + 2; if (!(repstr = dm_pool_zalloc(mem, len))) { log_error("dm_pool_alloc failed"); return NULL; } if (dm_snprintf(repstr, len, "%s%s/%s", lv->vg->cmd->dev_dir, lv->vg->name, lv->name) < 0) { log_error("lvpath snprintf failed"); return NULL; } return repstr; } char *lv_dmpath_dup(struct dm_pool *mem, const struct logical_volume *lv) { char *name; char *repstr; size_t len; if (!*lv->vg->name) return dm_pool_strdup(mem, ""); if (!(name = dm_build_dm_name(mem, lv->vg->name, lv->name, NULL))) { log_error("dm_build_dm_name failed"); return NULL; } len = strlen(dm_dir()) + strlen(name) + 2; if (!(repstr = dm_pool_zalloc(mem, len))) { log_error("dm_pool_alloc failed"); return NULL; } if (dm_snprintf(repstr, len, "%s/%s", dm_dir(), name) < 0) { log_error("lv_dmpath snprintf failed"); return NULL; } return repstr; } /* maybe factor a common function with lv_dmpath_dup */ char *lv_dmpath_suffix_dup(struct dm_pool *mem, const struct logical_volume *lv, const char *suffix) { char *name; char *repstr; size_t len; if (!*lv->vg->name) return dm_pool_strdup(mem, ""); if (!(name = dm_build_dm_name(mem, lv->vg->name, lv->name, NULL))) { log_error("dm_build_dm_name failed"); return NULL; } len = strlen(dm_dir()) + strlen(name) + strlen(suffix) + 2; if (!(repstr = dm_pool_zalloc(mem, len))) { log_error("dm_pool_alloc failed"); return NULL; } if (dm_snprintf(repstr, len, "%s/%s%s", dm_dir(), name, suffix) < 0) { log_error("lv_dmpath snprintf failed"); return NULL; } return repstr; } char *lv_uuid_dup(struct dm_pool *mem, const struct logical_volume *lv) { return id_format_and_copy(mem ? mem : lv->vg->vgmem, &lv->lvid.id[1]); } char *lv_tags_dup(const struct logical_volume *lv) { return tags_format_and_copy(lv->vg->vgmem, &lv->tags); } uint64_t lv_size(const struct logical_volume *lv) { return lv->size; } int lv_mirror_image_in_sync(const struct logical_volume *lv) { dm_percent_t percent; struct lv_segment *seg = first_seg(lv); struct lv_segment *mirror_seg; if (!lv_is_mirror_image(lv) || !seg || !(mirror_seg = find_mirror_seg(seg))) { log_error(INTERNAL_ERROR "Cannot find mirror segment."); return 0; } if (!lv_mirror_percent(lv->vg->cmd, mirror_seg->lv, 0, &percent, NULL)) return_0; return (percent == DM_PERCENT_100) ? 1 : 0; } int lv_raid_image_in_sync(const struct logical_volume *lv) { unsigned s; char *raid_health; struct lv_segment *seg, *raid_seg = NULL; /* * If the LV is not active locally, * it doesn't make sense to check status */ if (!lv_is_active(lv)) return 0; /* Assume not in-sync */ if (!lv_is_raid_image(lv)) { log_error(INTERNAL_ERROR "%s is not a RAID image", lv->name); return 0; } if ((seg = first_seg(lv))) raid_seg = get_only_segment_using_this_lv(seg->lv); if (!raid_seg) { log_error("Failed to find RAID segment for %s", lv->name); return 0; } if (!seg_is_raid(raid_seg)) { log_error("%s on %s is not a RAID segment", raid_seg->lv->name, lv->name); return 0; } /* Find out which sub-LV this is. */ for (s = 0; s < raid_seg->area_count; s++) if (seg_lv(raid_seg, s) == lv) break; if (s == raid_seg->area_count) { log_error(INTERNAL_ERROR "sub-LV %s was not found in raid segment", lv->name); return 0; } if (!lv_raid_dev_health(raid_seg->lv, &raid_health)) return_0; if (raid_health[s] == 'A') return 1; return 0; } static int lv_raid_integrity_image_in_sync(const struct logical_volume *lv_iorig) { struct logical_volume *lv_image = NULL; struct logical_volume *lv_raid = NULL; struct lv_segment *raid_seg = NULL; const struct seg_list *sl; char *raid_health; unsigned int s; int found = 0; if (!lv_is_active(lv_iorig)) return 0; /* Assume not in-sync */ /* Get top level raid LV from lv_iorig. */ /* step 1: get lv_image from lv_iorig */ dm_list_iterate_items(sl, &lv_iorig->segs_using_this_lv) { if (!sl->seg || !sl->seg->lv || !sl->seg->origin) continue; if (lv_is_integrity(sl->seg->lv) && (sl->seg->origin == lv_iorig)) { lv_image = sl->seg->lv; break; } } if (!lv_image) { log_error("No lv_image found for lv_iorig %s", lv_iorig->name); return 0; } /* step 2: get lv_raid from lv_image */ if ((raid_seg = get_only_segment_using_this_lv(lv_image))) lv_raid = raid_seg->lv; if (!lv_raid) { log_error("No lv_raid found for lv_image %s lv_iorig %s", lv_image->name, lv_iorig->name); return 0; } /* Figure out which image number this is in lv_raid. */ if (!(raid_seg = first_seg(lv_raid))) { log_error("No raid seg found for lv_raid %s lv_image %s lv_iorig %s", lv_raid->name, lv_image->name, lv_iorig->name); return 0; } for (s = 0; s < raid_seg->area_count; s++) { if (seg_lv(raid_seg, s) == lv_image) { found = 1; break; } } if (!found) { log_error("No seg area found for lv_raid %s lv_image %s lv_iorig %s", lv_raid->name, lv_image->name, lv_iorig->name); return 0; } if (!lv_raid_dev_health(lv_raid, &raid_health)) { log_error("No raid health for seg area %u lv_raid %s lv_image %s lv_iorig %s", s, lv_raid->name, lv_image->name, lv_iorig->name); return 0; } log_debug("raid health %c for seg area %u lv_raid %s lv_image %s lv_iorig %s", raid_health[s], s, lv_raid->name, lv_image->name, lv_iorig->name); if (raid_health[s] == 'A') return 1; return 0; } /* * _lv_raid_healthy * @lv: A RAID_IMAGE, RAID_META, or RAID logical volume. * * Returns: 1 if healthy, 0 if device is not health */ int lv_raid_healthy(const struct logical_volume *lv) { unsigned s; char *raid_health; struct lv_segment *seg, *raid_seg = NULL; /* * If the LV is not active locally, * it doesn't make sense to check status */ if (!lv_is_active(lv)) return 1; /* assume healthy */ if (!lv_is_raid_type(lv)) { log_error(INTERNAL_ERROR "%s is not of RAID type", lv->name); return 0; } if (lv_is_raid(lv)) raid_seg = first_seg(lv); else if ((seg = first_seg(lv))) raid_seg = get_only_segment_using_this_lv(seg->lv); if (!raid_seg) { log_error("Failed to find RAID segment for %s", lv->name); return 0; } if (!seg_is_raid(raid_seg)) { log_error(INTERNAL_ERROR "%s on %s is not a RAID segment.", raid_seg->lv->name, lv->name); return 0; } if (!lv_raid_dev_health(raid_seg->lv, &raid_health)) return_0; if (lv_is_raid(lv)) return (strchr(raid_health, 'D')) ? 0 : 1; /* Find out which sub-LV this is. */ for (s = 0; s < raid_seg->area_count; s++) if ((lv_is_raid_image(lv) && (seg_lv(raid_seg, s) == lv)) || (lv_is_raid_metadata(lv) && (seg_metalv(raid_seg, s) == lv))) break; if (s == raid_seg->area_count) { log_error(INTERNAL_ERROR "sub-LV %s was not found in raid segment", lv->name); return 0; } if (raid_health[s] == 'D') return 0; return 1; } /* Helper: check for any sub LVs after a disk removing reshape */ static int _sublvs_remove_after_reshape(const struct logical_volume *lv) { uint32_t s; struct lv_segment *seg = first_seg(lv); for (s = seg->area_count -1; s; s--) if (seg_lv(seg, s)->status & LV_REMOVE_AFTER_RESHAPE) return 1; return 0; } char *lv_attr_dup_with_info_and_seg_status(struct dm_pool *mem, const struct lv_with_info_and_seg_status *lvdm) { const struct logical_volume *lv = lvdm->lv; struct lv_segment *seg; char *repstr; if (!(repstr = dm_pool_zalloc(mem, 11))) { log_error("dm_pool_alloc failed"); return 0; } /* Blank if this is a "free space" LV. */ if (!*lv->name && !lv_is_historical(lv)) goto out; if (lv_is_pvmove(lv)) repstr[0] = 'p'; else if (lv->status & CONVERTING) repstr[0] = 'c'; /* Origin takes precedence over mirror and thin volume */ else if (lv_is_origin(lv) || lv_is_external_origin(lv)) repstr[0] = (lv_is_merging_origin(lv)) ? 'O' : 'o'; else if (lv_is_pool_metadata(lv) || lv_is_pool_metadata_spare(lv) || lv_is_raid_metadata(lv) || lv_is_integrity_metadata(lv)) repstr[0] = 'e'; else if (lv_is_cache_type(lv) || lv_is_writecache(lv)) repstr[0] = 'C'; else if (lv_is_integrity_origin(lv)) repstr[0] = lv_raid_integrity_image_in_sync(lv) ? 'i' : 'I'; else if (lv_is_integrity(lv)) repstr[0] = 'g'; else if (lv_is_raid(lv)) repstr[0] = (lv_is_not_synced(lv)) ? 'R' : 'r'; else if (lv_is_mirror(lv)) repstr[0] = (lv_is_not_synced(lv)) ? 'M' : 'm'; else if (lv_is_thin_volume(lv)) repstr[0] = lv_is_merging_origin(lv) ? 'O' : (lv_is_merging_thin_snapshot(lv) ? 'S' : 'V'); //else if (lv_is_vdo(lv)) // repstr[0] = 'V'; // TODO: Show 'V' like Virtual Thin ? // ATM shows 'v' as virtual target just like: error, zero else if (lv_is_vdo_pool(lv)) repstr[0] = 'd'; else if (lv_is_vdo_pool_data(lv)) repstr[0] = 'D'; else if (lv_is_virtual(lv)) repstr[0] = 'v'; else if (lv_is_thin_pool(lv)) repstr[0] = 't'; else if (lv_is_thin_pool_data(lv)) repstr[0] = 'T'; else if (lv_is_mirror_image(lv)) repstr[0] = (lv_mirror_image_in_sync(lv)) ? 'i' : 'I'; else if (lv_is_raid_image(lv)) /* * Visible RAID_IMAGES are sub-LVs that have been exposed for * top-level use by being split from the RAID array with * '--splitmirrors 1 --trackchanges'. They always report 'I'. */ repstr[0] = (!lv_is_visible(lv) && lv_raid_image_in_sync(lv)) ? 'i' : 'I'; else if (lv_is_mirror_log(lv)) repstr[0] = 'l'; else if (lv_is_cow(lv)) repstr[0] = (lv_is_merging_cow(lv)) ? 'S' : 's'; else if (lv_is_cache_origin(lv) || lv_is_writecache_origin(lv)) repstr[0] = 'o'; else repstr[0] = '-'; if (lv_is_pvmove(lv)) repstr[1] = '-'; else if (lv->status & LVM_WRITE) repstr[1] = 'w'; else if (lv->status & LVM_READ) repstr[1] = 'r'; else repstr[1] = '-'; repstr[2] = alloc_policy_char(lv->alloc); if (lv_is_locked(lv)) repstr[2] = toupper(repstr[2]); repstr[3] = (lv->status & FIXED_MINOR) ? 'm' : '-'; if (lv_is_historical(lv)) { repstr[4] = 'h'; repstr[5] = '-'; } else if (!activation() || !lvdm->info_ok || (lvdm->seg_status.type == SEG_STATUS_UNKNOWN)) { repstr[4] = 'X'; /* Unknown */ repstr[5] = 'X'; /* Unknown */ } else if (lvdm->info.exists) { if (lvdm->info.suspended) repstr[4] = 's'; /* Suspended */ else if (lvdm->info.live_table) repstr[4] = 'a'; /* Active */ else if (lvdm->info.inactive_table) repstr[4] = 'i'; /* Inactive with table */ else repstr[4] = 'd'; /* Inactive without table */ /* Snapshot dropped? */ if (lvdm->info.live_table && (lvdm->seg_status.type == SEG_STATUS_SNAPSHOT)) { if (lvdm->seg_status.snapshot->invalid) { if (lvdm->info.suspended) repstr[4] = 'S'; /* Susp Inv snapshot */ else repstr[4] = 'I'; /* Invalid snapshot */ } else if (lvdm->seg_status.snapshot->merge_failed) { if (lvdm->info.suspended) repstr[4] = 'M'; /* Susp snapshot merge failed */ else repstr[4] = 'm'; /* Snapshot merge failed */ } } /* 'c' when cache/thin-pool is active with needs_check flag * 'C' for suspend */ if ((lv_is_thin_pool(lv) && (lvdm->seg_status.type == SEG_STATUS_THIN_POOL) && lvdm->seg_status.thin_pool->needs_check) || (lv_is_cache(lv) && (lvdm->seg_status.type == SEG_STATUS_CACHE) && lvdm->seg_status.cache->needs_check)) repstr[4] = lvdm->info.suspended ? 'C' : 'c'; /* * 'R' indicates read-only activation of a device that * does not have metadata flagging it as read-only. */ if (repstr[1] != 'r' && lvdm->info.read_only) repstr[1] = 'R'; repstr[5] = (lvdm->info.open_count) ? 'o' : '-'; } else { repstr[4] = '-'; repstr[5] = '-'; } if (lv_is_thin_pool(lv) || lv_is_thin_volume(lv)) repstr[6] = 't'; else if (lv_is_cache_pool(lv) || lv_is_cache_vol(lv) || lv_is_cache(lv) || lv_is_cache_origin(lv) || lv_is_writecache(lv) || lv_is_writecache_origin(lv)) repstr[6] = 'C'; else if (lv_is_raid_type(lv)) repstr[6] = 'r'; else if (lv_is_mirror_type(lv) || lv_is_pvmove(lv)) repstr[6] = 'm'; else if (lv_is_cow(lv) || lv_is_origin(lv)) repstr[6] = 's'; else if (lv_has_unknown_segments(lv)) repstr[6] = 'u'; else if (lv_is_virtual(lv)) repstr[6] = 'v'; else repstr[6] = '-'; if (((lv_is_thin_volume(lv) && (seg = first_seg(lv)) && seg->pool_lv && (seg = first_seg(seg->pool_lv))) || (lv_is_thin_pool(lv) && (seg = first_seg(lv)))) && (seg->zero_new_blocks == THIN_ZERO_YES)) repstr[7] = 'z'; else repstr[7] = '-'; repstr[8] = '-'; /* TODO: also convert raid health * lv_is_raid_type() is to wide * NOTE: snapshot origin is 'mostly' showing it's layered status */ if (lv_is_partial(lv)) repstr[8] = 'p'; else if (lv_is_raid_type(lv)) { uint64_t n; char *sync_action; if (!activation()) repstr[8] = 'X'; /* Unknown */ else if (!lv_raid_healthy(lv)) repstr[8] = 'r'; /* RAID needs 'r'efresh */ else if (lv_is_raid(lv)) { if (lv_raid_mismatch_count(lv, &n) && n) repstr[8] = 'm'; /* RAID has 'm'ismatches */ else if (lv_raid_sync_action(lv, &sync_action) && !strcmp(sync_action, "reshape")) repstr[8] = 's'; /* LV is re's'haping */ else if (_sublvs_remove_after_reshape(lv)) repstr[8] = 'R'; /* sub-LV got freed from raid set by reshaping and has to be 'R'emoved */ } else if (lv->status & LV_WRITEMOSTLY) repstr[8] = 'w'; /* sub-LV has 'w'ritemostly */ else if (lv->status & LV_REMOVE_AFTER_RESHAPE) repstr[8] = 'R'; /* sub-LV got freed from raid set by reshaping and has to be 'R'emoved */ } else if (lvdm->seg_status.type == SEG_STATUS_CACHE) { if (lvdm->seg_status.cache->fail) repstr[8] = 'F'; else if (lvdm->seg_status.cache->read_only) repstr[8] = 'M'; } else if (lvdm->seg_status.type == SEG_STATUS_THIN_POOL) { if (lvdm->seg_status.thin_pool->fail) repstr[8] = 'F'; else if (lvdm->seg_status.thin_pool->out_of_data_space) repstr[8] = 'D'; else if (lvdm->seg_status.thin_pool->read_only) repstr[8] = 'M'; } else if (lvdm->seg_status.type == SEG_STATUS_THIN) { if (lvdm->seg_status.thin->fail) repstr[8] = 'F'; } else if (lvdm->seg_status.type == SEG_STATUS_WRITECACHE) { if (lvdm->seg_status.writecache->error) repstr[8] = 'E'; } else if (lvdm->seg_status.type == SEG_STATUS_UNKNOWN) repstr[8] = 'X'; /* Unknown */ if (lv->status & LV_ACTIVATION_SKIP) repstr[9] = 'k'; else repstr[9] = '-'; out: return repstr; } /* backward compatible internal API for lvm2api, TODO improve it */ char *lv_attr_dup(struct dm_pool *mem, const struct logical_volume *lv) { char *ret = NULL; struct lv_with_info_and_seg_status status = { .seg_status.type = SEG_STATUS_NONE, }; if (!(status.seg_status.mem = dm_pool_create("reporter_pool", 1024))) return_0; if (!(status.info_ok = lv_info_with_seg_status(lv->vg->cmd, first_seg(lv), &status, 1, 1))) goto_bad; ret = lv_attr_dup_with_info_and_seg_status(mem, &status); bad: dm_pool_destroy(status.seg_status.mem); return ret; } int lv_set_creation(struct logical_volume *lv, const char *hostname, uint64_t timestamp) { const char *hn; if (!hostname) { if (!_utsinit) { if (uname(&_utsname)) { log_error("uname failed: %s", strerror(errno)); memset(&_utsname, 0, sizeof(_utsname)); } _utsinit = 1; } hostname = _utsname.nodename; } if (!(hn = dm_hash_lookup(lv->vg->hostnames, hostname))) { if (!(hn = dm_pool_strdup(lv->vg->vgmem, hostname))) { log_error("Failed to duplicate hostname"); return 0; } if (!dm_hash_insert(lv->vg->hostnames, hostname, (void*)hn)) return_0; } lv->hostname = hn; lv->timestamp = timestamp ? : (uint64_t) time(NULL); return 1; } static char *_time_dup(struct cmd_context *cmd, struct dm_pool *mem, time_t ts, int iso_mode) { char buffer[4096]; struct tm *local_tm; const char *format = iso_mode ? DEFAULT_TIME_FORMAT : cmd->time_format; if (!ts || !(local_tm = localtime(&ts)) || !strftime(buffer, sizeof(buffer), format, local_tm)) buffer[0] = 0; return dm_pool_strdup(mem, buffer); } char *lv_creation_time_dup(struct dm_pool *mem, const struct logical_volume *lv, int iso_mode) { time_t ts = lv_is_historical(lv) ? (time_t) lv->this_glv->historical->timestamp : (time_t) lv->timestamp; return _time_dup(lv->vg->cmd, mem, ts, iso_mode); } char *lv_removal_time_dup(struct dm_pool *mem, const struct logical_volume *lv, int iso_mode) { time_t ts = lv_is_historical(lv) ? (time_t)lv->this_glv->historical->timestamp_removed : (time_t)0; return _time_dup(lv->vg->cmd, mem, ts, iso_mode); } char *lv_host_dup(struct dm_pool *mem, const struct logical_volume *lv) { return dm_pool_strdup(mem, lv->hostname ? : ""); } int lv_active_change(struct cmd_context *cmd, struct logical_volume *lv, enum activation_change activate) { const char *ay_with_mode = NULL; if (activate == CHANGE_ASY) ay_with_mode = "sh"; if (activate == CHANGE_AEY) ay_with_mode = "ex"; if (is_change_activating(activate) && !lockd_lv(cmd, lv, ay_with_mode, LDLV_PERSISTENT)) { log_error("Failed to lock logical volume %s.", display_lvname(lv)); return 0; } switch (activate) { case CHANGE_AN: case CHANGE_ALN: log_verbose("Deactivating logical volume %s.", display_lvname(lv)); if (!deactivate_lv(cmd, lv)) return_0; break; case CHANGE_ALY: case CHANGE_AAY: case CHANGE_AEY: case CHANGE_ASY: case CHANGE_AY: default: log_verbose("Activating logical volume %s.", display_lvname(lv)); if (!activate_lv(cmd, lv)) return_0; break; } if (!is_change_activating(activate) && !lockd_lv(cmd, lv, "un", LDLV_PERSISTENT)) log_error("Failed to unlock logical volume %s.", display_lvname(lv)); return 1; } char *lv_active_dup(struct dm_pool *mem, const struct logical_volume *lv) { const char *s; if (!activation()) { s = "unknown"; goto out; } if (!lv_is_active(lv)) s = ""; /* not active */ else s = "active"; out: return dm_pool_strdup(mem, s); } char *lv_profile_dup(struct dm_pool *mem, const struct logical_volume *lv) { const char *profile_name = lv->profile ? lv->profile->name : ""; return dm_pool_strdup(mem, profile_name); } char *lv_lock_args_dup(struct dm_pool *mem, const struct logical_volume *lv) { const char *lock_args = lv->lock_args ? lv->lock_args : ""; return dm_pool_strdup(mem, lock_args); } /* For given LV find recursively the LV which holds lock for it */ const struct logical_volume *lv_lock_holder(const struct logical_volume *lv) { const struct seg_list *sl; if (lv_is_cow(lv)) return lv_lock_holder(origin_from_cow(lv)); if (lv_is_thin_pool(lv) || lv_is_external_origin(lv)) { /* FIXME: Ensure cluster keeps thin-pool active exclusively. * External origin can be activated on more nodes (depends on type). */ if (!lv_is_active(lv)) /* Find any active LV from the pool or external origin */ dm_list_iterate_items(sl, &lv->segs_using_this_lv) if (lv_is_active(sl->seg->lv)) { log_debug_activation("Thin volume %s is active.", display_lvname(lv)); return sl->seg->lv; } return lv; } /* RAID changes visibility of split LVs but references them still as leg/meta */ if ((lv_is_raid_image(lv) || lv_is_raid_metadata(lv)) && lv_is_visible(lv)) return lv; if (lv_is_pvmove(lv)) return lv; /* For other types, by default look for the first user */ dm_list_iterate_items(sl, &lv->segs_using_this_lv) { /* FIXME: complete this exception list */ if (lv_is_thin_volume(lv) && lv_is_thin_volume(sl->seg->lv) && first_seg(lv)->pool_lv == sl->seg->pool_lv) continue; /* Skip thin snapshot */ if (lv_is_pending_delete(sl->seg->lv)) continue; /* Skip deleted LVs */ if (lv_is_cache_pool(sl->seg->lv) && !lv_is_used_cache_pool(sl->seg->lv)) continue; /* Skip unused cache-pool */ return lv_lock_holder(sl->seg->lv); } return lv; } struct profile *lv_config_profile(const struct logical_volume *lv) { return lv->profile ? : lv->vg->profile; } int lv_has_constant_stripes(struct logical_volume *lv) { uint32_t previous_area_count = 0; struct lv_segment *seg; dm_list_iterate_items(seg, &lv->segments) { if (!seg_is_striped(seg)) return 0; if (previous_area_count && previous_area_count != seg->area_count) return 0; previous_area_count = seg->area_count; } return 1; }