mirror of
git://sourceware.org/git/lvm2.git
synced 2024-12-22 17:35:59 +03:00
7b570840cd
The unlock call will fail in expected and normal cases, and should not cause the command to fail. (An actual unlock in the lock manager should never fail.)
3099 lines
81 KiB
C
3099 lines
81 KiB
C
/*
|
|
* Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved.
|
|
* Copyright (C) 2004-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "tools.h"
|
|
|
|
#include <sys/stat.h>
|
|
#include <signal.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/utsname.h>
|
|
|
|
struct device_id_list {
|
|
struct dm_list list;
|
|
struct device *dev;
|
|
char pvid[ID_LEN + 1];
|
|
};
|
|
|
|
const char *command_name(struct cmd_context *cmd)
|
|
{
|
|
return cmd->command->name;
|
|
}
|
|
|
|
static void _sigchld_handler(int sig __attribute__((unused)))
|
|
{
|
|
while (wait4(-1, NULL, WNOHANG | WUNTRACED, NULL) > 0) ;
|
|
}
|
|
|
|
/*
|
|
* returns:
|
|
* -1 if the fork failed
|
|
* 0 if the parent
|
|
* 1 if the child
|
|
*/
|
|
int become_daemon(struct cmd_context *cmd, int skip_lvm)
|
|
{
|
|
static const char devnull[] = "/dev/null";
|
|
int null_fd;
|
|
pid_t pid;
|
|
struct sigaction act = {
|
|
{_sigchld_handler},
|
|
.sa_flags = SA_NOCLDSTOP,
|
|
};
|
|
|
|
log_verbose("Forking background process: %s", cmd->cmd_line);
|
|
|
|
sigaction(SIGCHLD, &act, NULL);
|
|
|
|
if (!skip_lvm)
|
|
if (!sync_local_dev_names(cmd)) { /* Flush ops and reset dm cookie */
|
|
log_error("Failed to sync local devices before forking.");
|
|
return -1;
|
|
}
|
|
|
|
if ((pid = fork()) == -1) {
|
|
log_error("fork failed: %s", strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
/* Parent */
|
|
if (pid > 0)
|
|
return 0;
|
|
|
|
/* Child */
|
|
if (setsid() == -1)
|
|
log_error("Background process failed to setsid: %s",
|
|
strerror(errno));
|
|
|
|
/* Set this to avoid discarding output from background process */
|
|
// #define DEBUG_CHILD
|
|
|
|
#ifndef DEBUG_CHILD
|
|
if ((null_fd = open(devnull, O_RDWR)) == -1) {
|
|
log_sys_error("open", devnull);
|
|
_exit(ECMD_FAILED);
|
|
}
|
|
|
|
if ((dup2(null_fd, STDIN_FILENO) < 0) || /* reopen stdin */
|
|
(dup2(null_fd, STDOUT_FILENO) < 0) || /* reopen stdout */
|
|
(dup2(null_fd, STDERR_FILENO) < 0)) { /* reopen stderr */
|
|
log_sys_error("dup2", "redirect");
|
|
(void) close(null_fd);
|
|
_exit(ECMD_FAILED);
|
|
}
|
|
|
|
if (null_fd > STDERR_FILENO)
|
|
(void) close(null_fd);
|
|
|
|
init_verbose(VERBOSE_BASE_LEVEL);
|
|
#endif /* DEBUG_CHILD */
|
|
|
|
strncpy(*cmd->argv, "(lvm2)", strlen(*cmd->argv));
|
|
|
|
lvmetad_disconnect();
|
|
|
|
if (!skip_lvm) {
|
|
reset_locking();
|
|
lvmcache_destroy(cmd, 1, 1);
|
|
if (!lvmcache_init())
|
|
/* FIXME Clean up properly here */
|
|
_exit(ECMD_FAILED);
|
|
}
|
|
dev_close_all();
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Strip dev_dir if present
|
|
*/
|
|
const char *skip_dev_dir(struct cmd_context *cmd, const char *vg_name,
|
|
unsigned *dev_dir_found)
|
|
{
|
|
size_t devdir_len = strlen(cmd->dev_dir);
|
|
const char *dmdir = dm_dir() + devdir_len;
|
|
size_t dmdir_len = strlen(dmdir), vglv_sz;
|
|
char *vgname, *lvname, *layer, *vglv;
|
|
|
|
/* FIXME Do this properly */
|
|
if (*vg_name == '/')
|
|
while (vg_name[1] == '/')
|
|
vg_name++;
|
|
|
|
if (strncmp(vg_name, cmd->dev_dir, devdir_len)) {
|
|
if (dev_dir_found)
|
|
*dev_dir_found = 0;
|
|
} else {
|
|
if (dev_dir_found)
|
|
*dev_dir_found = 1;
|
|
|
|
vg_name += devdir_len;
|
|
while (*vg_name == '/')
|
|
vg_name++;
|
|
|
|
/* Reformat string if /dev/mapper found */
|
|
if (!strncmp(vg_name, dmdir, dmdir_len) && vg_name[dmdir_len] == '/') {
|
|
vg_name += dmdir_len + 1;
|
|
while (*vg_name == '/')
|
|
vg_name++;
|
|
|
|
if (!dm_split_lvm_name(cmd->mem, vg_name, &vgname, &lvname, &layer) ||
|
|
*layer) {
|
|
log_error("skip_dev_dir: Couldn't split up device name %s.",
|
|
vg_name);
|
|
return vg_name;
|
|
}
|
|
vglv_sz = strlen(vgname) + strlen(lvname) + 2;
|
|
if (!(vglv = dm_pool_alloc(cmd->mem, vglv_sz)) ||
|
|
dm_snprintf(vglv, vglv_sz, "%s%s%s", vgname,
|
|
*lvname ? "/" : "",
|
|
lvname) < 0) {
|
|
log_error("vg/lv string alloc failed.");
|
|
return vg_name;
|
|
}
|
|
return vglv;
|
|
}
|
|
}
|
|
|
|
return vg_name;
|
|
}
|
|
|
|
/*
|
|
* Three possible results:
|
|
* a) return 0, skip 0: take the VG, and cmd will end in success
|
|
* b) return 0, skip 1: skip the VG, and cmd will end in success
|
|
* c) return 1, skip *: skip the VG, and cmd will end in failure
|
|
*
|
|
* Case b is the special case, and includes the following:
|
|
* . The VG is inconsistent, and the command allows for inconsistent VGs.
|
|
* . The VG is clustered, the host cannot access clustered VG's,
|
|
* and the command option has been used to ignore clustered vgs.
|
|
*
|
|
* Case c covers the other errors returned when reading the VG.
|
|
* If *skip is 1, it's OK for the caller to read the list of PVs in the VG.
|
|
*/
|
|
static int _ignore_vg(struct volume_group *vg, const char *vg_name,
|
|
struct dm_list *arg_vgnames, int allow_inconsistent, int *skip)
|
|
{
|
|
uint32_t read_error = vg_read_error(vg);
|
|
*skip = 0;
|
|
|
|
if ((read_error & FAILED_INCONSISTENT) && allow_inconsistent)
|
|
read_error &= ~FAILED_INCONSISTENT; /* Check for other errors */
|
|
|
|
if ((read_error & FAILED_CLUSTERED) && vg->cmd->ignore_clustered_vgs) {
|
|
read_error &= ~FAILED_CLUSTERED; /* Check for other errors */
|
|
log_verbose("Skipping volume group %s", vg_name);
|
|
*skip = 1;
|
|
}
|
|
|
|
/*
|
|
* Commands that operate on "all vgs" shouldn't be bothered by
|
|
* skipping a foreign VG, and the command shouldn't fail when
|
|
* one is skipped. But, if the command explicitly asked to
|
|
* operate on a foreign VG and it's skipped, then the command
|
|
* would expect to fail.
|
|
*/
|
|
if (read_error & FAILED_SYSTEMID) {
|
|
if (arg_vgnames && str_list_match_item(arg_vgnames, vg->name)) {
|
|
log_error("Cannot access VG %s with system ID %s with %slocal system ID%s%s.",
|
|
vg->name, vg->system_id, vg->cmd->system_id ? "" : "unknown ",
|
|
vg->cmd->system_id ? " " : "", vg->cmd->system_id ? vg->cmd->system_id : "");
|
|
return 1;
|
|
} else {
|
|
read_error &= ~FAILED_SYSTEMID; /* Check for other errors */
|
|
log_verbose("Skipping foreign volume group %s", vg_name);
|
|
*skip = 1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Accessing a lockd VG when lvmlockd is not used is similar
|
|
* to accessing a foreign VG.
|
|
* This is also the point where a command fails if it failed
|
|
* to acquire the necessary lock from lvmlockd.
|
|
* The two cases are distinguished by FAILED_LOCK_TYPE (the
|
|
* VG lock_type requires lvmlockd), and FAILED_LOCK_MODE (the
|
|
* command failed to acquire the necessary lock.)
|
|
*/
|
|
if (read_error & (FAILED_LOCK_TYPE | FAILED_LOCK_MODE)) {
|
|
if (arg_vgnames && str_list_match_item(arg_vgnames, vg->name)) {
|
|
if (read_error & FAILED_LOCK_TYPE)
|
|
log_error("Cannot access VG %s with lock type %s that requires lvmlockd.",
|
|
vg->name, vg->lock_type);
|
|
/* For FAILED_LOCK_MODE, the error is printed in vg_read. */
|
|
return 1;
|
|
} else {
|
|
read_error &= ~FAILED_LOCK_TYPE; /* Check for other errors */
|
|
read_error &= ~FAILED_LOCK_MODE;
|
|
log_verbose("Skipping volume group %s", vg_name);
|
|
*skip = 1;
|
|
}
|
|
}
|
|
|
|
if (read_error == FAILED_CLUSTERED) {
|
|
*skip = 1;
|
|
stack; /* Error already logged */
|
|
return 1;
|
|
}
|
|
|
|
if (read_error != SUCCESS) {
|
|
*skip = 0;
|
|
log_error("Cannot process volume group %s", vg_name);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This functiona updates the "selected" arg only if last item processed
|
|
* is selected so this implements the "whole structure is selected if
|
|
* at least one of its items is selected".
|
|
*/
|
|
static void _update_selection_result(struct processing_handle *handle, int *selected)
|
|
{
|
|
if (!handle || !handle->selection_handle)
|
|
return;
|
|
|
|
if (handle->selection_handle->selected)
|
|
*selected = 1;
|
|
}
|
|
|
|
static void _set_final_selection_result(struct processing_handle *handle, int selected)
|
|
{
|
|
if (!handle || !handle->selection_handle)
|
|
return;
|
|
|
|
handle->selection_handle->selected = selected;
|
|
}
|
|
|
|
/*
|
|
* Metadata iteration functions
|
|
*/
|
|
int process_each_segment_in_pv(struct cmd_context *cmd,
|
|
struct volume_group *vg,
|
|
struct physical_volume *pv,
|
|
struct processing_handle *handle,
|
|
process_single_pvseg_fn_t process_single_pvseg)
|
|
{
|
|
struct pv_segment *pvseg;
|
|
int whole_selected = 0;
|
|
int ret_max = ECMD_PROCESSED;
|
|
int ret;
|
|
struct pv_segment _free_pv_segment = { .pv = pv };
|
|
|
|
if (dm_list_empty(&pv->segments)) {
|
|
ret = process_single_pvseg(cmd, NULL, &_free_pv_segment, handle);
|
|
if (ret != ECMD_PROCESSED)
|
|
stack;
|
|
if (ret > ret_max)
|
|
ret_max = ret;
|
|
} else {
|
|
dm_list_iterate_items(pvseg, &pv->segments) {
|
|
if (sigint_caught())
|
|
return_ECMD_FAILED;
|
|
|
|
ret = process_single_pvseg(cmd, vg, pvseg, handle);
|
|
_update_selection_result(handle, &whole_selected);
|
|
if (ret != ECMD_PROCESSED)
|
|
stack;
|
|
if (ret > ret_max)
|
|
ret_max = ret;
|
|
}
|
|
}
|
|
|
|
/* the PV is selected if at least one PV segment is selected */
|
|
_set_final_selection_result(handle, whole_selected);
|
|
return ret_max;
|
|
}
|
|
|
|
int process_each_segment_in_lv(struct cmd_context *cmd,
|
|
struct logical_volume *lv,
|
|
struct processing_handle *handle,
|
|
process_single_seg_fn_t process_single_seg)
|
|
{
|
|
struct lv_segment *seg;
|
|
int whole_selected = 0;
|
|
int ret_max = ECMD_PROCESSED;
|
|
int ret;
|
|
|
|
dm_list_iterate_items(seg, &lv->segments) {
|
|
if (sigint_caught())
|
|
return_ECMD_FAILED;
|
|
|
|
ret = process_single_seg(cmd, seg, handle);
|
|
_update_selection_result(handle, &whole_selected);
|
|
if (ret != ECMD_PROCESSED)
|
|
stack;
|
|
if (ret > ret_max)
|
|
ret_max = ret;
|
|
}
|
|
|
|
/* the LV is selected if at least one LV segment is selected */
|
|
_set_final_selection_result(handle, whole_selected);
|
|
return ret_max;
|
|
}
|
|
|
|
static const char *_extract_vgname(struct cmd_context *cmd, const char *lv_name,
|
|
const char **after)
|
|
{
|
|
const char *vg_name = lv_name;
|
|
char *st, *pos;
|
|
|
|
/* Strip dev_dir (optional) */
|
|
if (!(vg_name = skip_dev_dir(cmd, vg_name, NULL)))
|
|
return_0;
|
|
|
|
/* Require exactly one set of consecutive slashes */
|
|
if ((st = pos = strchr(vg_name, '/')))
|
|
while (*st == '/')
|
|
st++;
|
|
|
|
if (!st || strchr(st, '/')) {
|
|
log_error("\"%s\": Invalid path for Logical Volume.",
|
|
lv_name);
|
|
return 0;
|
|
}
|
|
|
|
if (!(vg_name = dm_pool_strndup(cmd->mem, vg_name, pos - vg_name))) {
|
|
log_error("Allocation of vg_name failed.");
|
|
return 0;
|
|
}
|
|
|
|
if (after)
|
|
*after = st;
|
|
|
|
return vg_name;
|
|
}
|
|
|
|
/*
|
|
* Extract default volume group name from environment
|
|
*/
|
|
static const char *_default_vgname(struct cmd_context *cmd)
|
|
{
|
|
const char *vg_path;
|
|
|
|
/* Take default VG from environment? */
|
|
vg_path = getenv("LVM_VG_NAME");
|
|
if (!vg_path)
|
|
return 0;
|
|
|
|
vg_path = skip_dev_dir(cmd, vg_path, NULL);
|
|
|
|
if (strchr(vg_path, '/')) {
|
|
log_error("\"%s\": Invalid environment var LVM_VG_NAME set for Volume Group.",
|
|
vg_path);
|
|
return 0;
|
|
}
|
|
|
|
return dm_pool_strdup(cmd->mem, vg_path);
|
|
}
|
|
|
|
/*
|
|
* Determine volume group name from a logical volume name
|
|
*/
|
|
const char *extract_vgname(struct cmd_context *cmd, const char *lv_name)
|
|
{
|
|
const char *vg_name = lv_name;
|
|
|
|
/* Path supplied? */
|
|
if (vg_name && strchr(vg_name, '/')) {
|
|
if (!(vg_name = _extract_vgname(cmd, lv_name, NULL)))
|
|
return_NULL;
|
|
|
|
return vg_name;
|
|
}
|
|
|
|
if (!(vg_name = _default_vgname(cmd))) {
|
|
if (lv_name)
|
|
log_error("Path required for Logical Volume \"%s\".",
|
|
lv_name);
|
|
return NULL;
|
|
}
|
|
|
|
return vg_name;
|
|
}
|
|
|
|
/*
|
|
* Process physical extent range specifiers
|
|
*/
|
|
static int _add_pe_range(struct dm_pool *mem, const char *pvname,
|
|
struct dm_list *pe_ranges, uint32_t start, uint32_t count)
|
|
{
|
|
struct pe_range *per;
|
|
|
|
log_debug("Adding PE range: start PE %" PRIu32 " length %" PRIu32
|
|
" on %s.", start, count, pvname);
|
|
|
|
/* Ensure no overlap with existing areas */
|
|
dm_list_iterate_items(per, pe_ranges) {
|
|
if (((start < per->start) && (start + count - 1 >= per->start)) ||
|
|
((start >= per->start) &&
|
|
(per->start + per->count - 1) >= start)) {
|
|
log_error("Overlapping PE ranges specified (%" PRIu32
|
|
"-%" PRIu32 ", %" PRIu32 "-%" PRIu32 ")"
|
|
" on %s.",
|
|
start, start + count - 1, per->start,
|
|
per->start + per->count - 1, pvname);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (!(per = dm_pool_alloc(mem, sizeof(*per)))) {
|
|
log_error("Allocation of list failed.");
|
|
return 0;
|
|
}
|
|
|
|
per->start = start;
|
|
per->count = count;
|
|
dm_list_add(pe_ranges, &per->list);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int _xstrtouint32(const char *s, char **p, int base, uint32_t *result)
|
|
{
|
|
unsigned long ul;
|
|
|
|
errno = 0;
|
|
ul = strtoul(s, p, base);
|
|
|
|
if (errno || *p == s || ul > UINT32_MAX)
|
|
return 0;
|
|
|
|
*result = ul;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int _parse_pes(struct dm_pool *mem, char *c, struct dm_list *pe_ranges,
|
|
const char *pvname, uint32_t size)
|
|
{
|
|
char *endptr;
|
|
uint32_t start, end, len;
|
|
|
|
/* Default to whole PV */
|
|
if (!c) {
|
|
if (!_add_pe_range(mem, pvname, pe_ranges, UINT32_C(0), size))
|
|
return_0;
|
|
return 1;
|
|
}
|
|
|
|
while (*c) {
|
|
if (*c != ':')
|
|
goto error;
|
|
|
|
c++;
|
|
|
|
/* Disallow :: and :\0 */
|
|
if (*c == ':' || !*c)
|
|
goto error;
|
|
|
|
/* Default to whole range */
|
|
start = UINT32_C(0);
|
|
end = size - 1;
|
|
|
|
/* Start extent given? */
|
|
if (isdigit(*c)) {
|
|
if (!_xstrtouint32(c, &endptr, 10, &start))
|
|
goto error;
|
|
c = endptr;
|
|
/* Just one number given? */
|
|
if (!*c || *c == ':')
|
|
end = start;
|
|
}
|
|
/* Range? */
|
|
if (*c == '-') {
|
|
c++;
|
|
if (isdigit(*c)) {
|
|
if (!_xstrtouint32(c, &endptr, 10, &end))
|
|
goto error;
|
|
c = endptr;
|
|
}
|
|
} else if (*c == '+') { /* Length? */
|
|
c++;
|
|
if (isdigit(*c)) {
|
|
if (!_xstrtouint32(c, &endptr, 10, &len))
|
|
goto error;
|
|
c = endptr;
|
|
end = start + (len ? (len - 1) : 0);
|
|
}
|
|
}
|
|
|
|
if (*c && *c != ':')
|
|
goto error;
|
|
|
|
if ((start > end) || (end > size - 1)) {
|
|
log_error("PE range error: start extent %" PRIu32 " to "
|
|
"end extent %" PRIu32 ".", start, end);
|
|
return 0;
|
|
}
|
|
|
|
if (!_add_pe_range(mem, pvname, pe_ranges, start, end - start + 1))
|
|
return_0;
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
error:
|
|
log_error("Physical extent parsing error at %s.", c);
|
|
return 0;
|
|
}
|
|
|
|
static int _create_pv_entry(struct dm_pool *mem, struct pv_list *pvl,
|
|
char *colon, int allocatable_only, struct dm_list *r)
|
|
{
|
|
const char *pvname;
|
|
struct pv_list *new_pvl = NULL, *pvl2;
|
|
struct dm_list *pe_ranges;
|
|
|
|
pvname = pv_dev_name(pvl->pv);
|
|
if (allocatable_only && !(pvl->pv->status & ALLOCATABLE_PV)) {
|
|
log_warn("Physical volume %s not allocatable.", pvname);
|
|
return 1;
|
|
}
|
|
|
|
if (allocatable_only && is_missing_pv(pvl->pv)) {
|
|
log_warn("Physical volume %s is missing.", pvname);
|
|
return 1;
|
|
}
|
|
|
|
if (allocatable_only &&
|
|
(pvl->pv->pe_count == pvl->pv->pe_alloc_count)) {
|
|
log_warn("No free extents on physical volume \"%s\".", pvname);
|
|
return 1;
|
|
}
|
|
|
|
dm_list_iterate_items(pvl2, r)
|
|
if (pvl->pv->dev == pvl2->pv->dev) {
|
|
new_pvl = pvl2;
|
|
break;
|
|
}
|
|
|
|
if (!new_pvl) {
|
|
if (!(new_pvl = dm_pool_alloc(mem, sizeof(*new_pvl)))) {
|
|
log_error("Unable to allocate physical volume list.");
|
|
return 0;
|
|
}
|
|
|
|
memcpy(new_pvl, pvl, sizeof(*new_pvl));
|
|
|
|
if (!(pe_ranges = dm_pool_alloc(mem, sizeof(*pe_ranges)))) {
|
|
log_error("Allocation of pe_ranges list failed.");
|
|
return 0;
|
|
}
|
|
dm_list_init(pe_ranges);
|
|
new_pvl->pe_ranges = pe_ranges;
|
|
dm_list_add(r, &new_pvl->list);
|
|
}
|
|
|
|
/* Determine selected physical extents */
|
|
if (!_parse_pes(mem, colon, new_pvl->pe_ranges, pv_dev_name(pvl->pv),
|
|
pvl->pv->pe_count))
|
|
return_0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
struct dm_list *create_pv_list(struct dm_pool *mem, struct volume_group *vg, int argc,
|
|
char **argv, int allocatable_only)
|
|
{
|
|
struct dm_list *r;
|
|
struct pv_list *pvl;
|
|
struct dm_list tagsl, arg_pvnames;
|
|
char *pvname = NULL;
|
|
char *colon, *at_sign, *tagname;
|
|
int i;
|
|
|
|
/* Build up list of PVs */
|
|
if (!(r = dm_pool_alloc(mem, sizeof(*r)))) {
|
|
log_error("Allocation of list failed");
|
|
return NULL;
|
|
}
|
|
dm_list_init(r);
|
|
|
|
dm_list_init(&tagsl);
|
|
dm_list_init(&arg_pvnames);
|
|
|
|
for (i = 0; i < argc; i++) {
|
|
dm_unescape_colons_and_at_signs(argv[i], &colon, &at_sign);
|
|
|
|
if (at_sign && (at_sign == argv[i])) {
|
|
tagname = at_sign + 1;
|
|
if (!validate_tag(tagname)) {
|
|
log_error("Skipping invalid tag %s.", tagname);
|
|
continue;
|
|
}
|
|
dm_list_iterate_items(pvl, &vg->pvs) {
|
|
if (str_list_match_item(&pvl->pv->tags,
|
|
tagname)) {
|
|
if (!_create_pv_entry(mem, pvl, NULL,
|
|
allocatable_only,
|
|
r))
|
|
return_NULL;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
pvname = argv[i];
|
|
|
|
if (colon && !(pvname = dm_pool_strndup(mem, pvname,
|
|
(unsigned) (colon - pvname)))) {
|
|
log_error("Failed to clone PV name.");
|
|
return NULL;
|
|
}
|
|
|
|
if (!(pvl = find_pv_in_vg(vg, pvname))) {
|
|
log_error("Physical Volume \"%s\" not found in "
|
|
"Volume Group \"%s\".", pvname, vg->name);
|
|
return NULL;
|
|
}
|
|
if (!_create_pv_entry(mem, pvl, colon, allocatable_only, r))
|
|
return_NULL;
|
|
}
|
|
|
|
if (dm_list_empty(r))
|
|
log_error("No specified PVs have space available.");
|
|
|
|
return dm_list_empty(r) ? NULL : r;
|
|
}
|
|
|
|
struct dm_list *clone_pv_list(struct dm_pool *mem, struct dm_list *pvsl)
|
|
{
|
|
struct dm_list *r;
|
|
struct pv_list *pvl, *new_pvl;
|
|
|
|
/* Build up list of PVs */
|
|
if (!(r = dm_pool_alloc(mem, sizeof(*r)))) {
|
|
log_error("Allocation of list failed.");
|
|
return NULL;
|
|
}
|
|
dm_list_init(r);
|
|
|
|
dm_list_iterate_items(pvl, pvsl) {
|
|
if (!(new_pvl = dm_pool_zalloc(mem, sizeof(*new_pvl)))) {
|
|
log_error("Unable to allocate physical volume list.");
|
|
return NULL;
|
|
}
|
|
|
|
memcpy(new_pvl, pvl, sizeof(*new_pvl));
|
|
dm_list_add(r, &new_pvl->list);
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
const char _pe_size_may_not_be_negative_msg[] = "Physical extent size may not be negative.";
|
|
|
|
int vgcreate_params_set_defaults(struct cmd_context *cmd,
|
|
struct vgcreate_params *vp_def,
|
|
struct volume_group *vg)
|
|
{
|
|
int64_t extent_size;
|
|
|
|
/* Only vgsplit sets vg */
|
|
if (vg) {
|
|
vp_def->vg_name = NULL;
|
|
vp_def->extent_size = vg->extent_size;
|
|
vp_def->max_pv = vg->max_pv;
|
|
vp_def->max_lv = vg->max_lv;
|
|
vp_def->alloc = vg->alloc;
|
|
vp_def->clustered = vg_is_clustered(vg);
|
|
vp_def->vgmetadatacopies = vg->mda_copies;
|
|
vp_def->system_id = vg->system_id; /* No need to clone this */
|
|
} else {
|
|
vp_def->vg_name = NULL;
|
|
extent_size = find_config_tree_int64(cmd,
|
|
allocation_physical_extent_size_CFG, NULL) * 2;
|
|
if (extent_size < 0) {
|
|
log_error(_pe_size_may_not_be_negative_msg);
|
|
return 0;
|
|
}
|
|
vp_def->extent_size = (uint32_t) extent_size;
|
|
vp_def->max_pv = DEFAULT_MAX_PV;
|
|
vp_def->max_lv = DEFAULT_MAX_LV;
|
|
vp_def->alloc = DEFAULT_ALLOC_POLICY;
|
|
vp_def->clustered = DEFAULT_CLUSTERED;
|
|
vp_def->vgmetadatacopies = DEFAULT_VGMETADATACOPIES;
|
|
vp_def->system_id = cmd->system_id;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Set members of struct vgcreate_params from cmdline arguments.
|
|
* Do preliminary validation with arg_*() interface.
|
|
* Further, more generic validation is done in validate_vgcreate_params().
|
|
* This function is to remain in tools directory.
|
|
*/
|
|
int vgcreate_params_set_from_args(struct cmd_context *cmd,
|
|
struct vgcreate_params *vp_new,
|
|
struct vgcreate_params *vp_def)
|
|
{
|
|
const char *system_id_arg_str;
|
|
const char *lock_type = NULL;
|
|
int locking_type;
|
|
int use_lvmlockd;
|
|
int use_clvmd;
|
|
lock_type_t lock_type_num;
|
|
|
|
vp_new->vg_name = skip_dev_dir(cmd, vp_def->vg_name, NULL);
|
|
vp_new->max_lv = arg_uint_value(cmd, maxlogicalvolumes_ARG,
|
|
vp_def->max_lv);
|
|
vp_new->max_pv = arg_uint_value(cmd, maxphysicalvolumes_ARG,
|
|
vp_def->max_pv);
|
|
vp_new->alloc = (alloc_policy_t) arg_uint_value(cmd, alloc_ARG, vp_def->alloc);
|
|
|
|
/* Units of 512-byte sectors */
|
|
vp_new->extent_size =
|
|
arg_uint_value(cmd, physicalextentsize_ARG, vp_def->extent_size);
|
|
|
|
if (arg_sign_value(cmd, physicalextentsize_ARG, SIGN_NONE) == SIGN_MINUS) {
|
|
log_error(_pe_size_may_not_be_negative_msg);
|
|
return 0;
|
|
}
|
|
|
|
if (arg_uint64_value(cmd, physicalextentsize_ARG, 0) > MAX_EXTENT_SIZE) {
|
|
log_error("Physical extent size cannot be larger than %s.",
|
|
display_size(cmd, (uint64_t) MAX_EXTENT_SIZE));
|
|
return 0;
|
|
}
|
|
|
|
if (arg_sign_value(cmd, maxlogicalvolumes_ARG, SIGN_NONE) == SIGN_MINUS) {
|
|
log_error("Max Logical Volumes may not be negative.");
|
|
return 0;
|
|
}
|
|
|
|
if (arg_sign_value(cmd, maxphysicalvolumes_ARG, SIGN_NONE) == SIGN_MINUS) {
|
|
log_error("Max Physical Volumes may not be negative.");
|
|
return 0;
|
|
}
|
|
|
|
if (arg_count(cmd, metadatacopies_ARG))
|
|
vp_new->vgmetadatacopies = arg_int_value(cmd, metadatacopies_ARG,
|
|
DEFAULT_VGMETADATACOPIES);
|
|
else if (arg_count(cmd, vgmetadatacopies_ARG))
|
|
vp_new->vgmetadatacopies = arg_int_value(cmd, vgmetadatacopies_ARG,
|
|
DEFAULT_VGMETADATACOPIES);
|
|
else
|
|
vp_new->vgmetadatacopies = find_config_tree_int(cmd, metadata_vgmetadatacopies_CFG, NULL);
|
|
|
|
if (!(system_id_arg_str = arg_str_value(cmd, systemid_ARG, NULL))) {
|
|
vp_new->system_id = vp_def->system_id;
|
|
} else {
|
|
if (!(vp_new->system_id = system_id_from_string(cmd, system_id_arg_str)))
|
|
return_0;
|
|
|
|
/* FIXME Take local/extra_system_ids into account */
|
|
if (vp_new->system_id && cmd->system_id &&
|
|
strcmp(vp_new->system_id, cmd->system_id)) {
|
|
if (*vp_new->system_id)
|
|
log_warn("VG with system ID %s might become inaccessible as local system ID is %s",
|
|
vp_new->system_id, cmd->system_id);
|
|
else
|
|
log_warn("WARNING: A VG without a system ID allows unsafe access from other hosts.");
|
|
}
|
|
}
|
|
|
|
if ((system_id_arg_str = arg_str_value(cmd, systemid_ARG, NULL))) {
|
|
vp_new->system_id = system_id_from_string(cmd, system_id_arg_str);
|
|
} else {
|
|
vp_new->system_id = vp_def->system_id;
|
|
}
|
|
|
|
if (system_id_arg_str) {
|
|
if (!vp_new->system_id || !vp_new->system_id[0])
|
|
log_warn("WARNING: A VG without a system ID allows unsafe access from other hosts.");
|
|
|
|
if (vp_new->system_id && cmd->system_id &&
|
|
strcmp(vp_new->system_id, cmd->system_id)) {
|
|
log_warn("VG with system ID %s might become inaccessible as local system ID is %s",
|
|
vp_new->system_id, cmd->system_id);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Locking: what kind of locking should be used for the
|
|
* new VG, and is it compatible with current lvm.conf settings.
|
|
*
|
|
* The end result is to set vp_new->lock_type to:
|
|
* none | clvm | dlm | sanlock.
|
|
*
|
|
* If 'vgcreate --lock-type <arg>' is set, the answer is given
|
|
* directly by <arg> which is one of none|clvm|dlm|sanlock.
|
|
*
|
|
* 'vgcreate --clustered y' is the way to create clvm VGs.
|
|
*
|
|
* 'vgcreate --shared' is the way to create lockd VGs.
|
|
* lock_type of sanlock or dlm is selected based on
|
|
* which lock manager is running.
|
|
*
|
|
*
|
|
* 1. Using neither clvmd nor lvmlockd.
|
|
* ------------------------------------------------
|
|
* lvm.conf:
|
|
* global/use_lvmlockd = 0
|
|
* global/locking_type = 1
|
|
*
|
|
* - no locking is enabled
|
|
* - clvmd is not used
|
|
* - lvmlockd is not used
|
|
* - VGs with CLUSTERED set are ignored (requires clvmd)
|
|
* - VGs with lockd type are ignored (requires lvmlockd)
|
|
* - vgcreate can create new VGs with lock_type none
|
|
* - 'vgcreate --clustered y' fails
|
|
* - 'vgcreate --shared' fails
|
|
* - 'vgcreate' (neither option) creates a local VG
|
|
*
|
|
* 2. Using clvmd.
|
|
* ------------------------------------------------
|
|
* lvm.conf:
|
|
* global/use_lvmlockd = 0
|
|
* global/locking_type = 3
|
|
*
|
|
* - locking through clvmd is enabled (traditional clvm config)
|
|
* - clvmd is used
|
|
* - lvmlockd is not used
|
|
* - VGs with CLUSTERED set can be used
|
|
* - VGs with lockd type are ignored (requires lvmlockd)
|
|
* - vgcreate can create new VGs with CLUSTERED status flag
|
|
* - 'vgcreate --clustered y' works
|
|
* - 'vgcreate --shared' fails
|
|
* - 'vgcreate' (neither option) creates a clvm VG
|
|
*
|
|
* 3. Using lvmlockd.
|
|
* ------------------------------------------------
|
|
* lvm.conf:
|
|
* global/use_lvmlockd = 1
|
|
* global/locking_type = 1
|
|
*
|
|
* - locking through lvmlockd is enabled
|
|
* - clvmd is not used
|
|
* - lvmlockd is used
|
|
* - VGs with CLUSTERED set are ignored (requires clvmd)
|
|
* - VGs with lockd type can be used
|
|
* - vgcreate can create new VGs with lock_type sanlock or dlm
|
|
* - 'vgcreate --clustered y' fails
|
|
* - 'vgcreate --shared' works
|
|
* - 'vgcreate' (neither option) creates a local VG
|
|
*/
|
|
|
|
locking_type = find_config_tree_int(cmd, global_locking_type_CFG, NULL);
|
|
use_lvmlockd = find_config_tree_bool(cmd, global_use_lvmlockd_CFG, NULL);
|
|
use_clvmd = (locking_type == 3);
|
|
|
|
if (arg_is_set(cmd, locktype_ARG)) {
|
|
if (arg_is_set(cmd, clustered_ARG) || arg_is_set(cmd, shared_ARG)) {
|
|
log_error("A lock type cannot be specified with --shared or --clustered.");
|
|
return 0;
|
|
}
|
|
lock_type = arg_str_value(cmd, locktype_ARG, "");
|
|
|
|
} else if (arg_is_set(cmd, clustered_ARG)) {
|
|
const char *arg_str = arg_str_value(cmd, clustered_ARG, "");
|
|
int clustery = strcmp(arg_str, "y") ? 0 : 1;
|
|
|
|
if (use_clvmd) {
|
|
lock_type = clustery ? "clvm" : "none";
|
|
|
|
} else if (use_lvmlockd) {
|
|
log_error("lvmlockd is configured, use --shared with lvmlockd, and --clustered with clvmd.");
|
|
return 0;
|
|
|
|
} else {
|
|
if (clustery) {
|
|
log_error("The --clustered option requires clvmd (locking_type=3).");
|
|
return 0;
|
|
} else {
|
|
lock_type = "none";
|
|
}
|
|
}
|
|
|
|
} else if (arg_is_set(cmd, shared_ARG)) {
|
|
if (use_lvmlockd) {
|
|
if (!(lock_type = lockd_running_lock_type(cmd))) {
|
|
log_error("Failed to detect a running lock manager to select lock type.");
|
|
return 0;
|
|
}
|
|
|
|
} else if (use_clvmd) {
|
|
log_error("Use --shared with lvmlockd, and --clustered with clvmd.");
|
|
return 0;
|
|
|
|
} else {
|
|
log_error("Using a shared lock type requires lvmlockd.");
|
|
return 0;
|
|
}
|
|
|
|
} else {
|
|
if (use_clvmd)
|
|
lock_type = locking_is_clustered() ? "clvm" : "none";
|
|
else
|
|
lock_type = "none";
|
|
}
|
|
|
|
/*
|
|
* Check that the lock_type is recognized, and is being
|
|
* used with the correct lvm.conf settings.
|
|
*/
|
|
lock_type_num = get_lock_type_from_string(lock_type);
|
|
|
|
switch (lock_type_num) {
|
|
case LOCK_TYPE_INVALID:
|
|
log_error("lock_type %s is invalid", lock_type);
|
|
return 0;
|
|
|
|
case LOCK_TYPE_SANLOCK:
|
|
case LOCK_TYPE_DLM:
|
|
if (!use_lvmlockd) {
|
|
log_error("Using a shared lock type requires lvmlockd.");
|
|
return 0;
|
|
}
|
|
break;
|
|
case LOCK_TYPE_CLVM:
|
|
if (!use_clvmd) {
|
|
log_error("Using clvm requires locking_type 3.");
|
|
return 0;
|
|
}
|
|
break;
|
|
case LOCK_TYPE_NONE:
|
|
break;
|
|
};
|
|
|
|
/*
|
|
* The vg is not owned by one host/system_id.
|
|
* Locking coordinates access from multiple hosts.
|
|
*/
|
|
if (lock_type_num == LOCK_TYPE_DLM || lock_type_num == LOCK_TYPE_SANLOCK || lock_type_num == LOCK_TYPE_CLVM)
|
|
vp_new->system_id = NULL;
|
|
|
|
vp_new->lock_type = lock_type;
|
|
|
|
if (lock_type_num == LOCK_TYPE_CLVM)
|
|
vp_new->clustered = 1;
|
|
else
|
|
vp_new->clustered = 0;
|
|
|
|
log_debug("Setting lock_type to %s", vp_new->lock_type);
|
|
return 1;
|
|
}
|
|
|
|
/* Shared code for changing activation state for vgchange/lvchange */
|
|
int lv_change_activate(struct cmd_context *cmd, struct logical_volume *lv,
|
|
activation_change_t activate)
|
|
{
|
|
int r = 1;
|
|
|
|
if (lv_is_cache_pool(lv)) {
|
|
if (is_change_activating(activate)) {
|
|
log_verbose("Skipping activation of cache pool %s.",
|
|
display_lvname(lv));
|
|
return 1;
|
|
}
|
|
if (!dm_list_empty(&lv->segs_using_this_lv)) {
|
|
log_verbose("Skipping deactivation of used cache pool %s.",
|
|
display_lvname(lv));
|
|
return 1;
|
|
}
|
|
/*
|
|
* Allow to pass only deactivation of unused cache pool.
|
|
* Useful only for recovery of failed zeroing of metadata LV.
|
|
*/
|
|
}
|
|
|
|
if (lv_is_merging_origin(lv)) {
|
|
/*
|
|
* For merging origin, its snapshot must be inactive.
|
|
* If it's still active and cannot be deactivated
|
|
* activation or deactivation of origin fails!
|
|
*
|
|
* When origin is deactivated and merging snapshot is thin
|
|
* it allows to deactivate origin, but still report error,
|
|
* since the thin snapshot remains active.
|
|
*
|
|
* User could retry to deactivate it with another
|
|
* deactivation of origin, which is the only visible LV
|
|
*/
|
|
if (!deactivate_lv(cmd, find_snapshot(lv)->lv)) {
|
|
if (is_change_activating(activate)) {
|
|
log_error("Refusing to activate merging \"%s\" while snapshot \"%s\" is still active.",
|
|
lv->name, find_snapshot(lv)->lv->name);
|
|
return 0;
|
|
}
|
|
|
|
log_error("Cannot fully deactivate merging origin \"%s\" while snapshot \"%s\" is still active.",
|
|
lv->name, find_snapshot(lv)->lv->name);
|
|
r = 0; /* and continue to deactivate origin... */
|
|
}
|
|
}
|
|
|
|
if (!lv_active_change(cmd, lv, activate, 0))
|
|
return_0;
|
|
|
|
return r;
|
|
}
|
|
|
|
int lv_refresh(struct cmd_context *cmd, struct logical_volume *lv)
|
|
{
|
|
if (!lv_refresh_suspend_resume(cmd, lv))
|
|
return_0;
|
|
|
|
/*
|
|
* check if snapshot merge should be polled
|
|
* - unfortunately: even though the dev_manager will clear
|
|
* the lv's merge attributes if a merge is not possible;
|
|
* it is clearing a different instance of the lv (as
|
|
* retrieved with lv_from_lvid)
|
|
* - fortunately: polldaemon will immediately shutdown if the
|
|
* origin doesn't have a status with a snapshot percentage
|
|
*/
|
|
if (background_polling() && lv_is_merging_origin(lv) && lv_is_active_locally(lv))
|
|
lv_spawn_background_polling(cmd, lv);
|
|
|
|
return 1;
|
|
}
|
|
|
|
int vg_refresh_visible(struct cmd_context *cmd, struct volume_group *vg)
|
|
{
|
|
struct lv_list *lvl;
|
|
int r = 1;
|
|
|
|
sigint_allow();
|
|
dm_list_iterate_items(lvl, &vg->lvs) {
|
|
if (sigint_caught()) {
|
|
r = 0;
|
|
stack;
|
|
break;
|
|
}
|
|
|
|
if (lv_is_visible(lvl->lv) && !lv_refresh(cmd, lvl->lv)) {
|
|
r = 0;
|
|
stack;
|
|
}
|
|
}
|
|
|
|
sigint_restore();
|
|
|
|
return r;
|
|
}
|
|
|
|
void lv_spawn_background_polling(struct cmd_context *cmd,
|
|
struct logical_volume *lv)
|
|
{
|
|
const char *pvname;
|
|
const struct logical_volume *lv_mirr = NULL;
|
|
|
|
if (lv_is_pvmove(lv))
|
|
lv_mirr = lv;
|
|
else if (lv_is_locked(lv))
|
|
lv_mirr = find_pvmove_lv_in_lv(lv);
|
|
|
|
if (lv_mirr &&
|
|
(pvname = get_pvmove_pvname_from_lv_mirr(lv_mirr))) {
|
|
log_verbose("Spawning background pvmove process for %s.",
|
|
pvname);
|
|
pvmove_poll(cmd, pvname, lv_mirr->lvid.s, lv_mirr->vg->name, lv_mirr->name, 1);
|
|
}
|
|
|
|
if (lv_is_converting(lv) || lv_is_merging(lv)) {
|
|
log_verbose("Spawning background lvconvert process for %s.",
|
|
lv->name);
|
|
lvconvert_poll(cmd, lv, 1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Intial sanity checking of non-recovery related command-line arguments.
|
|
*
|
|
* Output arguments:
|
|
* pp: structure allocated by caller, fields written / validated here
|
|
*/
|
|
int pvcreate_params_validate(struct cmd_context *cmd, int argc,
|
|
struct pvcreate_params *pp)
|
|
{
|
|
if (!argc) {
|
|
log_error("Please enter a physical volume path.");
|
|
return 0;
|
|
}
|
|
|
|
pp->yes = arg_count(cmd, yes_ARG);
|
|
pp->force = (force_t) arg_count(cmd, force_ARG);
|
|
|
|
if (arg_int_value(cmd, labelsector_ARG, 0) >= LABEL_SCAN_SECTORS) {
|
|
log_error("labelsector must be less than %lu.",
|
|
LABEL_SCAN_SECTORS);
|
|
return 0;
|
|
} else {
|
|
pp->labelsector = arg_int64_value(cmd, labelsector_ARG,
|
|
DEFAULT_LABELSECTOR);
|
|
}
|
|
|
|
if (!(cmd->fmt->features & FMT_MDAS) &&
|
|
(arg_count(cmd, pvmetadatacopies_ARG) ||
|
|
arg_count(cmd, metadatasize_ARG) ||
|
|
arg_count(cmd, dataalignment_ARG) ||
|
|
arg_count(cmd, dataalignmentoffset_ARG))) {
|
|
log_error("Metadata and data alignment parameters only "
|
|
"apply to text format.");
|
|
return 0;
|
|
}
|
|
|
|
if (!(cmd->fmt->features & FMT_BAS) &&
|
|
arg_count(cmd, bootloaderareasize_ARG)) {
|
|
log_error("Bootloader area parameters only "
|
|
"apply to text format.");
|
|
return 0;
|
|
}
|
|
|
|
if (arg_count(cmd, metadataignore_ARG))
|
|
pp->metadataignore = arg_int_value(cmd, metadataignore_ARG,
|
|
DEFAULT_PVMETADATAIGNORE);
|
|
else
|
|
pp->metadataignore = find_config_tree_bool(cmd, metadata_pvmetadataignore_CFG, NULL);
|
|
|
|
if (arg_count(cmd, pvmetadatacopies_ARG) &&
|
|
!arg_int_value(cmd, pvmetadatacopies_ARG, -1) &&
|
|
pp->metadataignore) {
|
|
log_error("metadataignore only applies to metadatacopies > 0");
|
|
return 0;
|
|
}
|
|
|
|
pp->zero = arg_int_value(cmd, zero_ARG, 1);
|
|
|
|
if (arg_sign_value(cmd, dataalignment_ARG, SIGN_NONE) == SIGN_MINUS) {
|
|
log_error("Physical volume data alignment may not be negative.");
|
|
return 0;
|
|
}
|
|
pp->data_alignment = arg_uint64_value(cmd, dataalignment_ARG, UINT64_C(0));
|
|
|
|
if (pp->data_alignment > UINT32_MAX) {
|
|
log_error("Physical volume data alignment is too big.");
|
|
return 0;
|
|
}
|
|
|
|
if (arg_sign_value(cmd, dataalignmentoffset_ARG, SIGN_NONE) == SIGN_MINUS) {
|
|
log_error("Physical volume data alignment offset may not be negative");
|
|
return 0;
|
|
}
|
|
pp->data_alignment_offset = arg_uint64_value(cmd, dataalignmentoffset_ARG, UINT64_C(0));
|
|
|
|
if (pp->data_alignment_offset > UINT32_MAX) {
|
|
log_error("Physical volume data alignment offset is too big.");
|
|
return 0;
|
|
}
|
|
|
|
if ((pp->data_alignment + pp->data_alignment_offset) &&
|
|
(pp->rp.pe_start != PV_PE_START_CALC)) {
|
|
if ((pp->data_alignment ? pp->rp.pe_start % pp->data_alignment : pp->rp.pe_start) != pp->data_alignment_offset) {
|
|
log_warn("WARNING: Ignoring data alignment %s"
|
|
" incompatible with restored pe_start value %s)",
|
|
display_size(cmd, pp->data_alignment + pp->data_alignment_offset),
|
|
display_size(cmd, pp->rp.pe_start));
|
|
pp->data_alignment = 0;
|
|
pp->data_alignment_offset = 0;
|
|
}
|
|
}
|
|
|
|
if (arg_sign_value(cmd, metadatasize_ARG, SIGN_NONE) == SIGN_MINUS) {
|
|
log_error("Metadata size may not be negative.");
|
|
return 0;
|
|
}
|
|
|
|
if (arg_sign_value(cmd, bootloaderareasize_ARG, SIGN_NONE) == SIGN_MINUS) {
|
|
log_error("Bootloader area size may not be negative.");
|
|
return 0;
|
|
}
|
|
|
|
pp->pvmetadatasize = arg_uint64_value(cmd, metadatasize_ARG, UINT64_C(0));
|
|
if (!pp->pvmetadatasize)
|
|
pp->pvmetadatasize = find_config_tree_int(cmd, metadata_pvmetadatasize_CFG, NULL);
|
|
|
|
pp->pvmetadatacopies = arg_int_value(cmd, pvmetadatacopies_ARG, -1);
|
|
if (pp->pvmetadatacopies < 0)
|
|
pp->pvmetadatacopies = find_config_tree_int(cmd, metadata_pvmetadatacopies_CFG, NULL);
|
|
|
|
pp->rp.ba_size = arg_uint64_value(cmd, bootloaderareasize_ARG, pp->rp.ba_size);
|
|
|
|
return 1;
|
|
}
|
|
|
|
int get_activation_monitoring_mode(struct cmd_context *cmd,
|
|
int *monitoring_mode)
|
|
{
|
|
*monitoring_mode = DEFAULT_DMEVENTD_MONITOR;
|
|
|
|
if (arg_count(cmd, monitor_ARG) &&
|
|
(arg_count(cmd, ignoremonitoring_ARG) ||
|
|
arg_count(cmd, sysinit_ARG))) {
|
|
log_error("--ignoremonitoring or --sysinit option not allowed with --monitor option.");
|
|
return 0;
|
|
}
|
|
|
|
if (arg_count(cmd, monitor_ARG))
|
|
*monitoring_mode = arg_int_value(cmd, monitor_ARG,
|
|
DEFAULT_DMEVENTD_MONITOR);
|
|
else if (is_static() || arg_count(cmd, ignoremonitoring_ARG) ||
|
|
arg_count(cmd, sysinit_ARG) ||
|
|
!find_config_tree_bool(cmd, activation_monitoring_CFG, NULL))
|
|
*monitoring_mode = DMEVENTD_MONITOR_IGNORE;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Read pool options from cmdline
|
|
*/
|
|
int get_pool_params(struct cmd_context *cmd,
|
|
const struct segment_type *segtype,
|
|
int *passed_args,
|
|
uint64_t *pool_metadata_size,
|
|
int *pool_metadata_spare,
|
|
uint32_t *chunk_size,
|
|
thin_discards_t *discards,
|
|
int *zero)
|
|
{
|
|
*passed_args = 0;
|
|
|
|
if (segtype_is_thin_pool(segtype) || segtype_is_thin(segtype)) {
|
|
if (arg_is_set(cmd, zero_ARG)) {
|
|
*passed_args |= PASS_ARG_ZERO;
|
|
*zero = arg_int_value(cmd, zero_ARG, 1);
|
|
log_very_verbose("%s pool zeroing.", *zero ? "Enabling" : "Disabling");
|
|
}
|
|
if (arg_is_set(cmd, discards_ARG)) {
|
|
*passed_args |= PASS_ARG_DISCARDS;
|
|
*discards = (thin_discards_t) arg_uint_value(cmd, discards_ARG, 0);
|
|
log_very_verbose("Setting pool discards to %s.",
|
|
get_pool_discards_name(*discards));
|
|
}
|
|
}
|
|
|
|
if (arg_from_list_is_negative(cmd, "may not be negative",
|
|
chunksize_ARG,
|
|
pooldatasize_ARG,
|
|
poolmetadatasize_ARG,
|
|
-1))
|
|
return_0;
|
|
|
|
if (arg_from_list_is_zero(cmd, "may not be zero",
|
|
chunksize_ARG,
|
|
pooldatasize_ARG,
|
|
poolmetadatasize_ARG,
|
|
-1))
|
|
return_0;
|
|
|
|
if (arg_is_set(cmd, chunksize_ARG)) {
|
|
*passed_args |= PASS_ARG_CHUNK_SIZE;
|
|
*chunk_size = arg_uint_value(cmd, chunksize_ARG, 0);
|
|
|
|
if (!validate_pool_chunk_size(cmd, segtype, *chunk_size))
|
|
return_0;
|
|
|
|
log_very_verbose("Setting pool chunk size to %s.",
|
|
display_size(cmd, *chunk_size));
|
|
}
|
|
|
|
if (arg_count(cmd, poolmetadatasize_ARG)) {
|
|
if (arg_sign_value(cmd, poolmetadatasize_ARG, SIGN_NONE) == SIGN_MINUS) {
|
|
log_error("Negative pool metadata size is invalid.");
|
|
return 0;
|
|
}
|
|
|
|
if (arg_count(cmd, poolmetadata_ARG)) {
|
|
log_error("Please specify either metadata logical volume or its size.");
|
|
return 0;
|
|
}
|
|
|
|
*passed_args |= PASS_ARG_POOL_METADATA_SIZE;
|
|
*pool_metadata_size = arg_uint64_value(cmd, poolmetadatasize_ARG,
|
|
UINT64_C(0));
|
|
} else if (arg_count(cmd, poolmetadata_ARG))
|
|
*passed_args |= PASS_ARG_POOL_METADATA_SIZE; /* fixed size */
|
|
|
|
/* TODO: default in lvm.conf ? */
|
|
*pool_metadata_spare = arg_int_value(cmd, poolmetadataspare_ARG,
|
|
DEFAULT_POOL_METADATA_SPARE);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Generic stripe parameter checks.
|
|
*/
|
|
static int _validate_stripe_params(struct cmd_context *cmd, uint32_t *stripes,
|
|
uint32_t *stripe_size)
|
|
{
|
|
if (*stripes == 1 && *stripe_size) {
|
|
log_print_unless_silent("Ignoring stripesize argument with single stripe.");
|
|
*stripe_size = 0;
|
|
}
|
|
|
|
if (*stripes > 1 && !*stripe_size) {
|
|
*stripe_size = find_config_tree_int(cmd, metadata_stripesize_CFG, NULL) * 2;
|
|
log_print_unless_silent("Using default stripesize %s.",
|
|
display_size(cmd, (uint64_t) *stripe_size));
|
|
}
|
|
|
|
if (*stripes < 1 || *stripes > MAX_STRIPES) {
|
|
log_error("Number of stripes (%d) must be between %d and %d.",
|
|
*stripes, 1, MAX_STRIPES);
|
|
return 0;
|
|
}
|
|
|
|
if (*stripes > 1 && (*stripe_size < STRIPE_SIZE_MIN ||
|
|
*stripe_size & (*stripe_size - 1))) {
|
|
log_error("Invalid stripe size %s.",
|
|
display_size(cmd, (uint64_t) *stripe_size));
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* The stripe size is limited by the size of a uint32_t, but since the
|
|
* value given by the user is doubled, and the final result must be a
|
|
* power of 2, we must divide UINT_MAX by four and add 1 (to round it
|
|
* up to the power of 2)
|
|
*/
|
|
int get_stripe_params(struct cmd_context *cmd, uint32_t *stripes, uint32_t *stripe_size)
|
|
{
|
|
/* stripes_long_ARG takes precedence (for lvconvert) */
|
|
*stripes = arg_uint_value(cmd, arg_count(cmd, stripes_long_ARG) ? stripes_long_ARG : stripes_ARG, 1);
|
|
|
|
*stripe_size = arg_uint_value(cmd, stripesize_ARG, 0);
|
|
if (*stripe_size) {
|
|
if (arg_sign_value(cmd, stripesize_ARG, SIGN_NONE) == SIGN_MINUS) {
|
|
log_error("Negative stripesize is invalid.");
|
|
return 0;
|
|
}
|
|
|
|
if (arg_uint64_value(cmd, stripesize_ARG, 0) > STRIPE_SIZE_LIMIT * 2) {
|
|
log_error("Stripe size cannot be larger than %s.",
|
|
display_size(cmd, (uint64_t) STRIPE_SIZE_LIMIT));
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return _validate_stripe_params(cmd, stripes, stripe_size);
|
|
}
|
|
|
|
static int _validate_cachepool_params(const char *name,
|
|
const struct dm_config_tree *settings)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
int get_cache_params(struct cmd_context *cmd,
|
|
const char **mode,
|
|
const char **name,
|
|
struct dm_config_tree **settings)
|
|
{
|
|
const char *str;
|
|
struct arg_value_group_list *group;
|
|
struct dm_config_tree *result = NULL, *prev = NULL, *current = NULL;
|
|
struct dm_config_node *cn;
|
|
int ok = 0;
|
|
|
|
if (mode)
|
|
*mode = arg_str_value(cmd, cachemode_ARG, NULL);
|
|
|
|
if (name)
|
|
*name = arg_str_value(cmd, cachepolicy_ARG, NULL);
|
|
|
|
if (!settings)
|
|
return 1;
|
|
|
|
dm_list_iterate_items(group, &cmd->arg_value_groups) {
|
|
if (!grouped_arg_is_set(group->arg_values, cachesettings_ARG))
|
|
continue;
|
|
|
|
if (!(current = dm_config_create()))
|
|
goto_out;
|
|
if (prev)
|
|
current->cascade = prev;
|
|
prev = current;
|
|
|
|
if (!(str = grouped_arg_str_value(group->arg_values,
|
|
cachesettings_ARG,
|
|
NULL)))
|
|
goto_out;
|
|
|
|
if (!dm_config_parse(current, str, str + strlen(str)))
|
|
goto_out;
|
|
}
|
|
|
|
if (!current)
|
|
return 1;
|
|
|
|
if (!(result = dm_config_flatten(current)))
|
|
goto_out;
|
|
|
|
if (result->root) {
|
|
if (!(cn = dm_config_create_node(result, "policy_settings")))
|
|
goto_out;
|
|
|
|
cn->child = result->root;
|
|
result->root = cn;
|
|
}
|
|
|
|
if (!_validate_cachepool_params(*name, result))
|
|
goto_out;
|
|
|
|
ok = 1;
|
|
out:
|
|
if (!ok && result) {
|
|
dm_config_destroy(result);
|
|
result = NULL;
|
|
}
|
|
while (prev) {
|
|
current = prev->cascade;
|
|
dm_config_destroy(prev);
|
|
prev = current;
|
|
}
|
|
|
|
*settings = result;
|
|
|
|
return ok;
|
|
}
|
|
|
|
/* FIXME move to lib */
|
|
static int _pv_change_tag(struct physical_volume *pv, const char *tag, int addtag)
|
|
{
|
|
if (addtag) {
|
|
if (!str_list_add(pv->fmt->cmd->mem, &pv->tags, tag)) {
|
|
log_error("Failed to add tag %s to physical volume %s.",
|
|
tag, pv_dev_name(pv));
|
|
return 0;
|
|
}
|
|
} else
|
|
str_list_del(&pv->tags, tag);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Set exactly one of VG, LV or PV */
|
|
int change_tag(struct cmd_context *cmd, struct volume_group *vg,
|
|
struct logical_volume *lv, struct physical_volume *pv, int arg)
|
|
{
|
|
const char *tag;
|
|
struct arg_value_group_list *current_group;
|
|
|
|
dm_list_iterate_items(current_group, &cmd->arg_value_groups) {
|
|
if (!grouped_arg_is_set(current_group->arg_values, arg))
|
|
continue;
|
|
|
|
if (!(tag = grouped_arg_str_value(current_group->arg_values, arg, NULL))) {
|
|
log_error("Failed to get tag.");
|
|
return 0;
|
|
}
|
|
|
|
if (vg && !vg_change_tag(vg, tag, arg == addtag_ARG))
|
|
return_0;
|
|
else if (lv && !lv_change_tag(lv, tag, arg == addtag_ARG))
|
|
return_0;
|
|
else if (pv && !_pv_change_tag(pv, tag, arg == addtag_ARG))
|
|
return_0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int process_each_label(struct cmd_context *cmd, int argc, char **argv,
|
|
struct processing_handle *handle,
|
|
process_single_label_fn_t process_single_label)
|
|
{
|
|
struct label *label;
|
|
struct dev_iter *iter;
|
|
struct device *dev;
|
|
|
|
int ret_max = ECMD_PROCESSED;
|
|
int ret;
|
|
int opt = 0;
|
|
|
|
if (argc) {
|
|
for (; opt < argc; opt++) {
|
|
if (!(dev = dev_cache_get(argv[opt], cmd->full_filter))) {
|
|
log_error("Failed to find device "
|
|
"\"%s\".", argv[opt]);
|
|
ret_max = ECMD_FAILED;
|
|
continue;
|
|
}
|
|
|
|
if (!label_read(dev, &label, 0)) {
|
|
log_error("No physical volume label read from %s.",
|
|
argv[opt]);
|
|
ret_max = ECMD_FAILED;
|
|
continue;
|
|
}
|
|
|
|
ret = process_single_label(cmd, label, handle);
|
|
|
|
if (ret > ret_max)
|
|
ret_max = ret;
|
|
|
|
if (sigint_caught())
|
|
break;
|
|
}
|
|
|
|
return ret_max;
|
|
}
|
|
|
|
if (!(iter = dev_iter_create(cmd->full_filter, 1))) {
|
|
log_error("dev_iter creation failed.");
|
|
return ECMD_FAILED;
|
|
}
|
|
|
|
while ((dev = dev_iter_get(iter)))
|
|
{
|
|
if (!label_read(dev, &label, 0))
|
|
continue;
|
|
|
|
ret = process_single_label(cmd, label, handle);
|
|
|
|
if (ret > ret_max)
|
|
ret_max = ret;
|
|
|
|
if (sigint_caught())
|
|
break;
|
|
}
|
|
|
|
dev_iter_destroy(iter);
|
|
|
|
return ret_max;
|
|
}
|
|
|
|
/*
|
|
* Parse persistent major minor parameters.
|
|
*
|
|
* --persistent is unspecified => state is deduced
|
|
* from presence of options --minor or --major.
|
|
*
|
|
* -Mn => --minor or --major not allowed.
|
|
*
|
|
* -My => --minor is required (and also --major on <=2.4)
|
|
*/
|
|
int get_and_validate_major_minor(const struct cmd_context *cmd,
|
|
const struct format_type *fmt,
|
|
int32_t *major, int32_t *minor)
|
|
{
|
|
if (arg_count(cmd, minor_ARG) > 1) {
|
|
log_error("Option --minor may not be repeated.");
|
|
return 0;
|
|
}
|
|
|
|
if (arg_count(cmd, major_ARG) > 1) {
|
|
log_error("Option -j|--major may not be repeated.");
|
|
return 0;
|
|
}
|
|
|
|
/* Check with default 'y' */
|
|
if (!arg_int_value(cmd, persistent_ARG, 1)) { /* -Mn */
|
|
if (arg_is_set(cmd, minor_ARG) || arg_is_set(cmd, major_ARG)) {
|
|
log_error("Options --major and --minor are incompatible with -Mn.");
|
|
return 0;
|
|
}
|
|
*major = *minor = -1;
|
|
return 1;
|
|
}
|
|
|
|
/* -1 cannot be entered as an argument for --major, --minor */
|
|
*major = arg_int_value(cmd, major_ARG, -1);
|
|
*minor = arg_int_value(cmd, minor_ARG, -1);
|
|
|
|
if (arg_is_set(cmd, persistent_ARG)) { /* -My */
|
|
if (*minor == -1) {
|
|
log_error("Please specify minor number with --minor when using -My.");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (!strncmp(cmd->kernel_vsn, "2.4.", 4)) {
|
|
/* Major is required for 2.4 */
|
|
if (arg_is_set(cmd, persistent_ARG) && *major < 0) {
|
|
log_error("Please specify major number with --major when using -My.");
|
|
return 0;
|
|
}
|
|
} else {
|
|
if (*major != -1) {
|
|
log_warn("WARNING: Ignoring supplied major number %d - "
|
|
"kernel assigns major numbers dynamically. "
|
|
"Using major number %d instead.",
|
|
*major, cmd->dev_types->device_mapper_major);
|
|
}
|
|
/* Stay with dynamic major:minor if minor is not specified. */
|
|
*major = (*minor == -1) ? -1 : cmd->dev_types->device_mapper_major;
|
|
}
|
|
|
|
if ((*minor != -1) && !validate_major_minor(cmd, fmt, *major, *minor))
|
|
return_0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Validate lvname parameter
|
|
*
|
|
* If it contains vgname, it is extracted from lvname.
|
|
* If there is passed vgname, it is compared whether its the same name.
|
|
*/
|
|
int validate_lvname_param(struct cmd_context *cmd, const char **vg_name,
|
|
const char **lv_name)
|
|
{
|
|
const char *vgname;
|
|
const char *lvname;
|
|
|
|
if (!lv_name || !*lv_name)
|
|
return 1; /* NULL lvname is ok */
|
|
|
|
/* If contains VG name, extract it. */
|
|
if (strchr(*lv_name, (int) '/')) {
|
|
if (!(vgname = _extract_vgname(cmd, *lv_name, &lvname)))
|
|
return_0;
|
|
|
|
if (!*vg_name)
|
|
*vg_name = vgname;
|
|
else if (strcmp(vgname, *vg_name)) {
|
|
log_error("Please use a single volume group name "
|
|
"(\"%s\" or \"%s\").", vgname, *vg_name);
|
|
return 0;
|
|
}
|
|
|
|
*lv_name = lvname;
|
|
}
|
|
|
|
if (!validate_name(*lv_name)) {
|
|
log_error("Logical volume name \"%s\" is invalid.",
|
|
*lv_name);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Validate lvname parameter
|
|
* This name must follow restriction rules on prefixes and suffixes.
|
|
*
|
|
* If it contains vgname, it is extracted from lvname.
|
|
* If there is passed vgname, it is compared whether its the same name.
|
|
*/
|
|
int validate_restricted_lvname_param(struct cmd_context *cmd, const char **vg_name,
|
|
const char **lv_name)
|
|
{
|
|
if (!validate_lvname_param(cmd, vg_name, lv_name))
|
|
return_0;
|
|
|
|
if (lv_name && *lv_name && !apply_lvname_restrictions(*lv_name))
|
|
return_0;
|
|
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Extract list of VG names and list of tags from command line arguments.
|
|
*/
|
|
static int _get_arg_vgnames(struct cmd_context *cmd,
|
|
int argc, char **argv,
|
|
unsigned one_vgname_arg,
|
|
struct dm_list *arg_vgnames,
|
|
struct dm_list *arg_tags)
|
|
{
|
|
int opt = 0;
|
|
int ret_max = ECMD_PROCESSED;
|
|
const char *vg_name;
|
|
|
|
log_verbose("Using volume group(s) on command line.");
|
|
|
|
for (; opt < argc; opt++) {
|
|
vg_name = argv[opt];
|
|
|
|
if (*vg_name == '@') {
|
|
if (one_vgname_arg) {
|
|
log_error("This command does not yet support a tag to identify a Volume Group.");
|
|
return EINVALID_CMD_LINE;
|
|
}
|
|
|
|
if (!validate_tag(vg_name + 1)) {
|
|
log_error("Skipping invalid tag: %s", vg_name);
|
|
if (ret_max < EINVALID_CMD_LINE)
|
|
ret_max = EINVALID_CMD_LINE;
|
|
continue;
|
|
}
|
|
|
|
if (!str_list_add(cmd->mem, arg_tags,
|
|
dm_pool_strdup(cmd->mem, vg_name + 1))) {
|
|
log_error("strlist allocation failed.");
|
|
return ECMD_FAILED;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
vg_name = skip_dev_dir(cmd, vg_name, NULL);
|
|
if (strchr(vg_name, '/')) {
|
|
log_error("Invalid volume group name %s.", vg_name);
|
|
if (ret_max < EINVALID_CMD_LINE)
|
|
ret_max = EINVALID_CMD_LINE;
|
|
if (one_vgname_arg)
|
|
break;
|
|
continue;
|
|
}
|
|
|
|
if (!str_list_add(cmd->mem, arg_vgnames,
|
|
dm_pool_strdup(cmd->mem, vg_name))) {
|
|
log_error("strlist allocation failed.");
|
|
return ECMD_FAILED;
|
|
}
|
|
|
|
if (one_vgname_arg)
|
|
break;
|
|
}
|
|
|
|
return ret_max;
|
|
}
|
|
|
|
struct processing_handle *init_processing_handle(struct cmd_context *cmd)
|
|
{
|
|
struct processing_handle *handle;
|
|
|
|
if (!(handle = dm_pool_zalloc(cmd->mem, sizeof(struct processing_handle)))) {
|
|
log_error("_init_processing_handle: failed to allocate memory for processing handle");
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* For any reporting tool, the internal_report_for_select is reset to 0
|
|
* automatically because the internal reporting/selection is simply not
|
|
* needed - the reporting/selection is already a part of the code path
|
|
* used there.
|
|
*
|
|
* *The internal report for select is only needed for non-reporting tools!*
|
|
*/
|
|
handle->internal_report_for_select = arg_is_set(cmd, select_ARG);
|
|
|
|
return handle;
|
|
}
|
|
|
|
int init_selection_handle(struct cmd_context *cmd, struct processing_handle *handle,
|
|
report_type_t initial_report_type)
|
|
{
|
|
struct selection_handle *sh;
|
|
|
|
if (!(sh = dm_pool_zalloc(cmd->mem, sizeof(struct selection_handle)))) {
|
|
log_error("_init_selection_handle: failed to allocate memory for selection handle");
|
|
return 0;
|
|
}
|
|
|
|
sh->report_type = initial_report_type;
|
|
if (!(sh->selection_rh = report_init_for_selection(cmd, &sh->report_type,
|
|
arg_str_value(cmd, select_ARG, NULL)))) {
|
|
dm_pool_free(cmd->mem, sh);
|
|
return_0;
|
|
}
|
|
|
|
handle->selection_handle = sh;
|
|
return 1;
|
|
}
|
|
|
|
void destroy_processing_handle(struct cmd_context *cmd, struct processing_handle *handle)
|
|
{
|
|
if (handle) {
|
|
if (handle->selection_handle && handle->selection_handle->selection_rh)
|
|
dm_report_free(handle->selection_handle->selection_rh);
|
|
dm_pool_free(cmd->mem, handle);
|
|
}
|
|
}
|
|
|
|
|
|
int select_match_vg(struct cmd_context *cmd, struct processing_handle *handle,
|
|
struct volume_group *vg, int *selected)
|
|
{
|
|
struct selection_handle *sh = handle->selection_handle;
|
|
|
|
if (!handle->internal_report_for_select) {
|
|
*selected = 1;
|
|
return 1;
|
|
}
|
|
|
|
sh->orig_report_type = VGS;
|
|
|
|
if (!report_for_selection(cmd, sh, NULL, vg, NULL)) {
|
|
log_error("Selection failed for VG %s.", vg->name);
|
|
return 0;
|
|
}
|
|
|
|
sh->orig_report_type = 0;
|
|
*selected = sh->selected;
|
|
|
|
return 1;
|
|
}
|
|
|
|
int select_match_lv(struct cmd_context *cmd, struct processing_handle *handle,
|
|
struct volume_group *vg, struct logical_volume *lv, int *selected)
|
|
{
|
|
struct selection_handle *sh = handle->selection_handle;
|
|
|
|
if (!handle->internal_report_for_select) {
|
|
*selected = 1;
|
|
return 1;
|
|
}
|
|
|
|
sh->orig_report_type = LVS;
|
|
|
|
if (!report_for_selection(cmd, sh, NULL, vg, lv)) {
|
|
log_error("Selection failed for LV %s.", lv->name);
|
|
return 0;
|
|
}
|
|
|
|
sh->orig_report_type = 0;
|
|
*selected = sh->selected;
|
|
|
|
return 1;
|
|
}
|
|
|
|
int select_match_pv(struct cmd_context *cmd, struct processing_handle *handle,
|
|
struct volume_group *vg, struct physical_volume *pv, int *selected)
|
|
{
|
|
struct selection_handle *sh = handle->selection_handle;
|
|
|
|
if (!handle->internal_report_for_select) {
|
|
*selected = 1;
|
|
return 1;
|
|
}
|
|
|
|
sh->orig_report_type = PVS;
|
|
|
|
if (!report_for_selection(cmd, sh, pv, vg, NULL)) {
|
|
log_error("Selection failed for PV %s.", dev_name(pv->dev));
|
|
return 0;
|
|
}
|
|
|
|
sh->orig_report_type = 0;
|
|
*selected = sh->selected;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int _process_vgnameid_list(struct cmd_context *cmd, uint32_t flags,
|
|
struct dm_list *vgnameids_to_process,
|
|
struct dm_list *arg_vgnames,
|
|
struct dm_list *arg_tags,
|
|
struct processing_handle *handle,
|
|
process_single_vg_fn_t process_single_vg)
|
|
{
|
|
struct volume_group *vg;
|
|
struct vgnameid_list *vgnl;
|
|
const char *vg_name;
|
|
const char *vg_uuid;
|
|
uint32_t lockd_state = 0;
|
|
int selected;
|
|
int whole_selected = 0;
|
|
int ret_max = ECMD_PROCESSED;
|
|
int ret;
|
|
int skip;
|
|
int process_all = 0;
|
|
|
|
/*
|
|
* If no VG names or tags were supplied, then process all VGs.
|
|
*/
|
|
if (dm_list_empty(arg_vgnames) && dm_list_empty(arg_tags))
|
|
process_all = 1;
|
|
|
|
/*
|
|
* FIXME If one_vgname_arg, only proceed if exactly one VG matches tags or selection.
|
|
*/
|
|
dm_list_iterate_items(vgnl, vgnameids_to_process) {
|
|
if (sigint_caught())
|
|
return_ECMD_FAILED;
|
|
|
|
vg_name = vgnl->vg_name;
|
|
vg_uuid = vgnl->vgid;
|
|
skip = 0;
|
|
|
|
if (!lockd_vg(cmd, vg_name, NULL, 0, &lockd_state)) {
|
|
ret_max = ECMD_FAILED;
|
|
continue;
|
|
}
|
|
|
|
vg = vg_read(cmd, vg_name, vg_uuid, flags, lockd_state);
|
|
if (_ignore_vg(vg, vg_name, arg_vgnames, flags & READ_ALLOW_INCONSISTENT, &skip)) {
|
|
stack;
|
|
ret_max = ECMD_FAILED;
|
|
goto endvg;
|
|
}
|
|
if (skip)
|
|
goto endvg;
|
|
|
|
/* Process this VG? */
|
|
if ((process_all ||
|
|
(!dm_list_empty(arg_vgnames) && str_list_match_item(arg_vgnames, vg_name)) ||
|
|
(!dm_list_empty(arg_tags) && str_list_match_list(arg_tags, &vg->tags, NULL))) &&
|
|
select_match_vg(cmd, handle, vg, &selected) && selected) {
|
|
ret = process_single_vg(cmd, vg_name, vg, handle);
|
|
_update_selection_result(handle, &whole_selected);
|
|
if (ret != ECMD_PROCESSED)
|
|
stack;
|
|
if (ret > ret_max)
|
|
ret_max = ret;
|
|
}
|
|
|
|
if (!vg_read_error(vg))
|
|
unlock_vg(cmd, vg_name);
|
|
endvg:
|
|
release_vg(vg);
|
|
if (!lockd_vg(cmd, vg_name, "un", 0, &lockd_state))
|
|
stack;
|
|
}
|
|
|
|
/* the VG is selected if at least one LV is selected */
|
|
_set_final_selection_result(handle, whole_selected);
|
|
return ret_max;
|
|
}
|
|
|
|
/*
|
|
* Copy the contents of a str_list of VG names to a name list, filling
|
|
* in the vgid with NULL (unknown).
|
|
*/
|
|
static int _copy_str_to_vgnameid_list(struct cmd_context *cmd, struct dm_list *sll,
|
|
struct dm_list *vgnll)
|
|
{
|
|
const char *vgname;
|
|
struct dm_str_list *sl;
|
|
struct vgnameid_list *vgnl;
|
|
|
|
dm_list_iterate_items(sl, sll) {
|
|
vgname = sl->str;
|
|
|
|
vgnl = dm_pool_alloc(cmd->mem, sizeof(*vgnl));
|
|
if (!vgnl) {
|
|
log_error("vgnameid_list allocation failed.");
|
|
return ECMD_FAILED;
|
|
}
|
|
|
|
vgnl->vgid = NULL;
|
|
vgnl->vg_name = dm_pool_strdup(cmd->mem, vgname);
|
|
|
|
dm_list_add(vgnll, &vgnl->list);
|
|
}
|
|
|
|
return ECMD_PROCESSED;
|
|
}
|
|
|
|
/*
|
|
* Call process_single_vg() for each VG selected by the command line arguments.
|
|
*/
|
|
int process_each_vg(struct cmd_context *cmd, int argc, char **argv,
|
|
uint32_t flags, struct processing_handle *handle,
|
|
process_single_vg_fn_t process_single_vg)
|
|
{
|
|
int handle_supplied = handle != NULL;
|
|
struct dm_list arg_tags; /* str_list */
|
|
struct dm_list arg_vgnames; /* str_list */
|
|
struct dm_list vgnameids_on_system; /* vgnameid_list */
|
|
struct dm_list vgnameids_to_process; /* vgnameid_list */
|
|
|
|
int enable_all_vgs = (cmd->command->flags & ALL_VGS_IS_DEFAULT);
|
|
unsigned one_vgname_arg = (flags & ONE_VGNAME_ARG);
|
|
int ret;
|
|
|
|
/* Disable error in vg_read so we can print it from ignore_vg. */
|
|
cmd->vg_read_print_access_error = 0;
|
|
|
|
dm_list_init(&arg_tags);
|
|
dm_list_init(&arg_vgnames);
|
|
dm_list_init(&vgnameids_on_system);
|
|
dm_list_init(&vgnameids_to_process);
|
|
|
|
/*
|
|
* Find any VGs or tags explicitly provided on the command line.
|
|
*/
|
|
if ((ret = _get_arg_vgnames(cmd, argc, argv, one_vgname_arg, &arg_vgnames, &arg_tags)) != ECMD_PROCESSED)
|
|
goto_out;
|
|
|
|
/*
|
|
* Obtain the complete list of VGs present on the system if it is needed because:
|
|
* any tags were supplied and need resolving; or
|
|
* no VG names were given and the command defaults to processing all VGs.
|
|
*/
|
|
if ((dm_list_empty(&arg_vgnames) && enable_all_vgs) || !dm_list_empty(&arg_tags)) {
|
|
/* Needed for a current listing of the global VG namespace. */
|
|
if (!lockd_gl(cmd, "sh", 0)) {
|
|
ret = ECMD_FAILED;
|
|
goto_out;
|
|
}
|
|
|
|
if (!get_vgnameids(cmd, &vgnameids_on_system, NULL, 0))
|
|
goto_out;
|
|
}
|
|
|
|
if (dm_list_empty(&arg_vgnames) && dm_list_empty(&vgnameids_on_system)) {
|
|
/* FIXME Should be log_print, but suppressed for reporting cmds */
|
|
log_verbose("No volume groups found.");
|
|
ret = ECMD_PROCESSED;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* If we obtained a full list of VGs on the system, we need to work through them all;
|
|
* otherwise we can merely work through the VG names provided.
|
|
*/
|
|
if (!dm_list_empty(&vgnameids_on_system))
|
|
dm_list_splice(&vgnameids_to_process, &vgnameids_on_system);
|
|
else if ((ret = _copy_str_to_vgnameid_list(cmd, &arg_vgnames, &vgnameids_to_process)) != ECMD_PROCESSED)
|
|
goto_out;
|
|
|
|
if (!handle && !(handle = init_processing_handle(cmd)))
|
|
goto_out;
|
|
|
|
if (handle->internal_report_for_select && !handle->selection_handle &&
|
|
!init_selection_handle(cmd, handle, VGS))
|
|
goto_out;
|
|
|
|
ret = _process_vgnameid_list(cmd, flags, &vgnameids_to_process,
|
|
&arg_vgnames, &arg_tags, handle, process_single_vg);
|
|
out:
|
|
if (!handle_supplied)
|
|
destroy_processing_handle(cmd, handle);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int process_each_lv_in_vg(struct cmd_context *cmd, struct volume_group *vg,
|
|
struct dm_list *arg_lvnames, const struct dm_list *tags_in,
|
|
int stop_on_error,
|
|
struct processing_handle *handle,
|
|
process_single_lv_fn_t process_single_lv)
|
|
{
|
|
int ret_max = ECMD_PROCESSED;
|
|
int ret = 0;
|
|
int selected;
|
|
int whole_selected = 0;
|
|
int handle_supplied = handle != NULL;
|
|
unsigned process_lv;
|
|
unsigned process_all = 0;
|
|
unsigned tags_supplied = 0;
|
|
unsigned lvargs_supplied = 0;
|
|
struct lv_list *lvl;
|
|
struct dm_str_list *sl;
|
|
struct dm_list final_lvs;
|
|
struct lv_list *final_lvl;
|
|
|
|
dm_list_init(&final_lvs);
|
|
|
|
if (!vg_check_status(vg, EXPORTED_VG)) {
|
|
ret_max = ECMD_FAILED;
|
|
goto_out;
|
|
}
|
|
|
|
if (tags_in && !dm_list_empty(tags_in))
|
|
tags_supplied = 1;
|
|
|
|
if (arg_lvnames && !dm_list_empty(arg_lvnames))
|
|
lvargs_supplied = 1;
|
|
|
|
if (!handle && !(handle = init_processing_handle(cmd))) {
|
|
ret_max = ECMD_FAILED;
|
|
goto_out;
|
|
}
|
|
|
|
if (handle->internal_report_for_select && !handle->selection_handle &&
|
|
!init_selection_handle(cmd, handle, LVS)) {
|
|
ret_max = ECMD_FAILED;
|
|
goto_out;
|
|
}
|
|
|
|
/* Process all LVs in this VG if no restrictions given
|
|
* or if VG tags match. */
|
|
if ((!tags_supplied && !lvargs_supplied) ||
|
|
(tags_supplied && str_list_match_list(tags_in, &vg->tags, NULL)))
|
|
process_all = 1;
|
|
|
|
dm_list_iterate_items(lvl, &vg->lvs) {
|
|
if (sigint_caught()) {
|
|
ret_max = ECMD_FAILED;
|
|
goto_out;
|
|
}
|
|
|
|
if (lvl->lv->status & SNAPSHOT)
|
|
continue;
|
|
|
|
/* Skip availability change for non-virt snaps when processing all LVs */
|
|
/* FIXME: pass process_all to process_single_lv() */
|
|
if (process_all && arg_count(cmd, activate_ARG) &&
|
|
lv_is_cow(lvl->lv) && !lv_is_virtual_origin(origin_from_cow(lvl->lv)))
|
|
continue;
|
|
|
|
if (lv_is_virtual_origin(lvl->lv) && !arg_count(cmd, all_ARG)) {
|
|
if (lvargs_supplied &&
|
|
str_list_match_item(arg_lvnames, lvl->lv->name))
|
|
log_print_unless_silent("Ignoring virtual origin logical volume %s.",
|
|
display_lvname(lvl->lv));
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Only let hidden LVs through if --all was used or the LVs
|
|
* were specifically named on the command line.
|
|
*/
|
|
if (!lvargs_supplied && !lv_is_visible(lvl->lv) && !arg_count(cmd, all_ARG))
|
|
continue;
|
|
|
|
/*
|
|
* Only let sanlock LV through if --all was used or if
|
|
* it is named on the command line.
|
|
*/
|
|
if (lv_is_lockd_sanlock_lv(lvl->lv)) {
|
|
if (arg_count(cmd, all_ARG) ||
|
|
(lvargs_supplied && str_list_match_item(arg_lvnames, lvl->lv->name))) {
|
|
log_very_verbose("Processing lockd_sanlock_lv %s/%s.", vg->name, lvl->lv->name);
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* process the LV if one of the following:
|
|
* - process_all is set
|
|
* - LV name matches a supplied LV name
|
|
* - LV tag matches a supplied LV tag
|
|
* - LV matches the selection
|
|
*/
|
|
|
|
process_lv = process_all;
|
|
|
|
if (lvargs_supplied && str_list_match_item(arg_lvnames, lvl->lv->name)) {
|
|
/* Remove LV from list of unprocessed LV names */
|
|
str_list_del(arg_lvnames, lvl->lv->name);
|
|
process_lv = 1;
|
|
}
|
|
|
|
if (!process_lv && tags_supplied && str_list_match_list(tags_in, &lvl->lv->tags, NULL))
|
|
process_lv = 1;
|
|
|
|
process_lv = process_lv && select_match_lv(cmd, handle, vg, lvl->lv, &selected) && selected;
|
|
|
|
if (sigint_caught()) {
|
|
ret_max = ECMD_FAILED;
|
|
goto_out;
|
|
}
|
|
|
|
if (!process_lv)
|
|
continue;
|
|
|
|
log_very_verbose("Adding %s/%s to the list of LVs to be processed.", vg->name, lvl->lv->name);
|
|
|
|
if (!(final_lvl = dm_pool_zalloc(vg->vgmem, sizeof(struct lv_list)))) {
|
|
log_error("Failed to allocate final LV list item.");
|
|
ret_max = ECMD_FAILED;
|
|
goto_out;
|
|
}
|
|
final_lvl->lv = lvl->lv;
|
|
dm_list_add(&final_lvs, &final_lvl->list);
|
|
}
|
|
|
|
dm_list_iterate_items(lvl, &final_lvs) {
|
|
/*
|
|
* FIXME: Once we have index over vg->removed_lvs, check directly
|
|
* LV presence there and remove LV_REMOVE flag/lv_is_removed fn
|
|
* as they won't be needed anymore.
|
|
*/
|
|
if (lv_is_removed(lvl->lv))
|
|
continue;
|
|
|
|
log_very_verbose("Processing LV %s in VG %s.", lvl->lv->name, vg->name);
|
|
|
|
ret = process_single_lv(cmd, lvl->lv, handle);
|
|
if (handle_supplied)
|
|
_update_selection_result(handle, &whole_selected);
|
|
if (ret != ECMD_PROCESSED)
|
|
stack;
|
|
if (ret > ret_max)
|
|
ret_max = ret;
|
|
|
|
if (stop_on_error && ret != ECMD_PROCESSED)
|
|
goto_out;
|
|
}
|
|
|
|
if (lvargs_supplied) {
|
|
/*
|
|
* FIXME: lvm supports removal of LV with all its dependencies
|
|
* this leads to miscalculation that depends on the order of args.
|
|
*/
|
|
dm_list_iterate_items(sl, arg_lvnames) {
|
|
log_error("Failed to find logical volume \"%s/%s\"",
|
|
vg->name, sl->str);
|
|
if (ret_max < ECMD_FAILED)
|
|
ret_max = ECMD_FAILED;
|
|
}
|
|
}
|
|
out:
|
|
if (!handle_supplied)
|
|
destroy_processing_handle(cmd, handle);
|
|
else
|
|
_set_final_selection_result(handle, whole_selected);
|
|
return ret_max;
|
|
}
|
|
|
|
/*
|
|
* If arg is tag, add it to arg_tags
|
|
* else the arg is either vgname or vgname/lvname:
|
|
* - add the vgname of each arg to arg_vgnames
|
|
* - if arg has no lvname, add just vgname arg_lvnames,
|
|
* it represents all lvs in the vg
|
|
* - if arg has lvname, add vgname/lvname to arg_lvnames
|
|
*/
|
|
static int _get_arg_lvnames(struct cmd_context *cmd,
|
|
int argc, char **argv,
|
|
struct dm_list *arg_vgnames,
|
|
struct dm_list *arg_lvnames,
|
|
struct dm_list *arg_tags)
|
|
{
|
|
int opt = 0;
|
|
int ret_max = ECMD_PROCESSED;
|
|
char *vglv;
|
|
size_t vglv_sz;
|
|
const char *vgname;
|
|
const char *lv_name;
|
|
const char *tmp_lv_name;
|
|
const char *vgname_def;
|
|
unsigned dev_dir_found;
|
|
|
|
log_verbose("Using logical volume(s) on command line.");
|
|
|
|
for (; opt < argc; opt++) {
|
|
lv_name = argv[opt];
|
|
dev_dir_found = 0;
|
|
|
|
/* Do we have a tag or vgname or lvname? */
|
|
vgname = lv_name;
|
|
|
|
if (*vgname == '@') {
|
|
if (!validate_tag(vgname + 1)) {
|
|
log_error("Skipping invalid tag %s.", vgname);
|
|
continue;
|
|
}
|
|
if (!str_list_add(cmd->mem, arg_tags,
|
|
dm_pool_strdup(cmd->mem, vgname + 1))) {
|
|
log_error("strlist allocation failed.");
|
|
return ECMD_FAILED;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/* FIXME Jumbled parsing */
|
|
vgname = skip_dev_dir(cmd, vgname, &dev_dir_found);
|
|
|
|
if (*vgname == '/') {
|
|
log_error("\"%s\": Invalid path for Logical Volume.",
|
|
argv[opt]);
|
|
if (ret_max < ECMD_FAILED)
|
|
ret_max = ECMD_FAILED;
|
|
continue;
|
|
}
|
|
lv_name = vgname;
|
|
if ((tmp_lv_name = strchr(vgname, '/'))) {
|
|
/* Must be an LV */
|
|
lv_name = tmp_lv_name;
|
|
while (*lv_name == '/')
|
|
lv_name++;
|
|
if (!(vgname = extract_vgname(cmd, vgname))) {
|
|
if (ret_max < ECMD_FAILED) {
|
|
stack;
|
|
ret_max = ECMD_FAILED;
|
|
}
|
|
continue;
|
|
}
|
|
} else if (!dev_dir_found &&
|
|
(vgname_def = _default_vgname(cmd)))
|
|
vgname = vgname_def;
|
|
else
|
|
lv_name = NULL;
|
|
|
|
if (!str_list_add(cmd->mem, arg_vgnames,
|
|
dm_pool_strdup(cmd->mem, vgname))) {
|
|
log_error("strlist allocation failed.");
|
|
return ECMD_FAILED;
|
|
}
|
|
|
|
if (!lv_name) {
|
|
if (!str_list_add(cmd->mem, arg_lvnames,
|
|
dm_pool_strdup(cmd->mem, vgname))) {
|
|
log_error("strlist allocation failed.");
|
|
return ECMD_FAILED;
|
|
}
|
|
} else {
|
|
vglv_sz = strlen(vgname) + strlen(lv_name) + 2;
|
|
if (!(vglv = dm_pool_alloc(cmd->mem, vglv_sz)) ||
|
|
dm_snprintf(vglv, vglv_sz, "%s/%s", vgname, lv_name) < 0) {
|
|
log_error("vg/lv string alloc failed.");
|
|
return ECMD_FAILED;
|
|
}
|
|
if (!str_list_add(cmd->mem, arg_lvnames, vglv)) {
|
|
log_error("strlist allocation failed.");
|
|
return ECMD_FAILED;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret_max;
|
|
}
|
|
|
|
static int _process_lv_vgnameid_list(struct cmd_context *cmd, uint32_t flags,
|
|
struct dm_list *vgnameids_to_process,
|
|
struct dm_list *arg_vgnames,
|
|
struct dm_list *arg_lvnames,
|
|
struct dm_list *arg_tags,
|
|
struct processing_handle *handle,
|
|
process_single_lv_fn_t process_single_lv)
|
|
{
|
|
struct volume_group *vg;
|
|
struct vgnameid_list *vgnl;
|
|
struct dm_str_list *sl;
|
|
struct dm_list *tags_arg;
|
|
struct dm_list lvnames;
|
|
uint32_t lockd_state = 0;
|
|
const char *vg_name;
|
|
const char *vg_uuid;
|
|
const char *vgn;
|
|
const char *lvn;
|
|
int ret_max = ECMD_PROCESSED;
|
|
int ret;
|
|
int skip;
|
|
|
|
dm_list_iterate_items(vgnl, vgnameids_to_process) {
|
|
if (sigint_caught())
|
|
return_ECMD_FAILED;
|
|
|
|
vg_name = vgnl->vg_name;
|
|
vg_uuid = vgnl->vgid;
|
|
skip = 0;
|
|
|
|
/*
|
|
* arg_lvnames contains some elements that are just "vgname"
|
|
* which means process all lvs in the vg. Other elements
|
|
* are "vgname/lvname" which means process only the select
|
|
* lvs in the vg.
|
|
*/
|
|
tags_arg = arg_tags;
|
|
dm_list_init(&lvnames); /* LVs to be processed in this VG */
|
|
|
|
dm_list_iterate_items(sl, arg_lvnames) {
|
|
vgn = sl->str;
|
|
lvn = strchr(vgn, '/');
|
|
|
|
if (!lvn && !strcmp(vgn, vg_name)) {
|
|
/* Process all LVs in this VG */
|
|
tags_arg = NULL;
|
|
dm_list_init(&lvnames);
|
|
break;
|
|
}
|
|
|
|
if (lvn && !strncmp(vgn, vg_name, strlen(vg_name)) &&
|
|
strlen(vg_name) == (size_t) (lvn - vgn)) {
|
|
if (!str_list_add(cmd->mem, &lvnames,
|
|
dm_pool_strdup(cmd->mem, lvn + 1))) {
|
|
log_error("strlist allocation failed.");
|
|
return ECMD_FAILED;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!lockd_vg(cmd, vg_name, NULL, 0, &lockd_state)) {
|
|
ret_max = ECMD_FAILED;
|
|
continue;
|
|
}
|
|
|
|
vg = vg_read(cmd, vg_name, vg_uuid, flags, lockd_state);
|
|
if (_ignore_vg(vg, vg_name, arg_vgnames, flags & READ_ALLOW_INCONSISTENT, &skip)) {
|
|
stack;
|
|
ret_max = ECMD_FAILED;
|
|
goto endvg;
|
|
|
|
}
|
|
if (skip)
|
|
goto endvg;
|
|
|
|
ret = process_each_lv_in_vg(cmd, vg, &lvnames, tags_arg, 0,
|
|
handle, process_single_lv);
|
|
if (ret != ECMD_PROCESSED)
|
|
stack;
|
|
if (ret > ret_max)
|
|
ret_max = ret;
|
|
|
|
unlock_vg(cmd, vg_name);
|
|
endvg:
|
|
release_vg(vg);
|
|
if (!lockd_vg(cmd, vg_name, "un", 0, &lockd_state))
|
|
stack;
|
|
}
|
|
|
|
return ret_max;
|
|
}
|
|
|
|
/*
|
|
* Call process_single_lv() for each LV selected by the command line arguments.
|
|
*/
|
|
int process_each_lv(struct cmd_context *cmd, int argc, char **argv, uint32_t flags,
|
|
struct processing_handle *handle, process_single_lv_fn_t process_single_lv)
|
|
{
|
|
int handle_supplied = handle != NULL;
|
|
struct dm_list arg_tags; /* str_list */
|
|
struct dm_list arg_vgnames; /* str_list */
|
|
struct dm_list arg_lvnames; /* str_list */
|
|
struct dm_list vgnameids_on_system; /* vgnameid_list */
|
|
struct dm_list vgnameids_to_process; /* vgnameid_list */
|
|
|
|
int enable_all_vgs = (cmd->command->flags & ALL_VGS_IS_DEFAULT);
|
|
int need_vgnameids = 0;
|
|
int ret;
|
|
|
|
/* Disable error in vg_read so we can print it from ignore_vg. */
|
|
cmd->vg_read_print_access_error = 0;
|
|
|
|
dm_list_init(&arg_tags);
|
|
dm_list_init(&arg_vgnames);
|
|
dm_list_init(&arg_lvnames);
|
|
dm_list_init(&vgnameids_on_system);
|
|
dm_list_init(&vgnameids_to_process);
|
|
|
|
/*
|
|
* Find any LVs, VGs or tags explicitly provided on the command line.
|
|
*/
|
|
if ((ret = _get_arg_lvnames(cmd, argc, argv, &arg_vgnames, &arg_lvnames, &arg_tags) != ECMD_PROCESSED))
|
|
goto_out;
|
|
|
|
if (!handle && !(handle = init_processing_handle(cmd)))
|
|
goto_out;
|
|
|
|
if (handle->internal_report_for_select && !handle->selection_handle &&
|
|
!init_selection_handle(cmd, handle, LVS))
|
|
goto_out;
|
|
|
|
/*
|
|
* Obtain the complete list of VGs present on the system if it is needed because:
|
|
* any tags were supplied and need resolving; or
|
|
* no VG names were given and the select option needs resolving; or
|
|
* no VG names were given and the command defaults to processing all VGs.
|
|
*/
|
|
if (!dm_list_empty(&arg_tags))
|
|
need_vgnameids = 1;
|
|
else if (dm_list_empty(&arg_vgnames) && enable_all_vgs)
|
|
need_vgnameids = 1;
|
|
else if (dm_list_empty(&arg_vgnames) && handle->internal_report_for_select)
|
|
need_vgnameids = 1;
|
|
|
|
if (need_vgnameids) {
|
|
/* Needed for a current listing of the global VG namespace. */
|
|
if (!lockd_gl(cmd, "sh", 0)) {
|
|
ret = ECMD_FAILED;
|
|
goto_out;
|
|
}
|
|
|
|
if (!get_vgnameids(cmd, &vgnameids_on_system, NULL, 0))
|
|
goto_out;
|
|
}
|
|
|
|
if (dm_list_empty(&arg_vgnames) && dm_list_empty(&vgnameids_on_system)) {
|
|
/* FIXME Should be log_print, but suppressed for reporting cmds */
|
|
log_verbose("No volume groups found.");
|
|
ret = ECMD_PROCESSED;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* If we obtained a full list of VGs on the system, we need to work through them all;
|
|
* otherwise we can merely work through the VG names provided.
|
|
*/
|
|
if (!dm_list_empty(&vgnameids_on_system))
|
|
dm_list_splice(&vgnameids_to_process, &vgnameids_on_system);
|
|
else if ((ret = _copy_str_to_vgnameid_list(cmd, &arg_vgnames, &vgnameids_to_process)) != ECMD_PROCESSED)
|
|
goto_out;
|
|
|
|
ret = _process_lv_vgnameid_list(cmd, flags, &vgnameids_to_process, &arg_vgnames, &arg_lvnames,
|
|
&arg_tags, handle, process_single_lv);
|
|
out:
|
|
if (!handle_supplied)
|
|
destroy_processing_handle(cmd, handle);
|
|
return ret;
|
|
}
|
|
|
|
static int _get_arg_pvnames(struct cmd_context *cmd,
|
|
int argc, char **argv,
|
|
struct dm_list *arg_pvnames,
|
|
struct dm_list *arg_tags)
|
|
{
|
|
int opt = 0;
|
|
char *at_sign, *tagname;
|
|
char *arg_name;
|
|
int ret_max = ECMD_PROCESSED;
|
|
|
|
log_verbose("Using physical volume(s) on command line.");
|
|
|
|
for (; opt < argc; opt++) {
|
|
arg_name = argv[opt];
|
|
|
|
dm_unescape_colons_and_at_signs(arg_name, NULL, &at_sign);
|
|
if (at_sign && (at_sign == arg_name)) {
|
|
tagname = at_sign + 1;
|
|
|
|
if (!validate_tag(tagname)) {
|
|
log_error("Skipping invalid tag %s.", tagname);
|
|
if (ret_max < EINVALID_CMD_LINE)
|
|
ret_max = EINVALID_CMD_LINE;
|
|
continue;
|
|
}
|
|
if (!str_list_add(cmd->mem, arg_tags,
|
|
dm_pool_strdup(cmd->mem, tagname))) {
|
|
log_error("strlist allocation failed.");
|
|
return ECMD_FAILED;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (!str_list_add(cmd->mem, arg_pvnames,
|
|
dm_pool_strdup(cmd->mem, arg_name))) {
|
|
log_error("strlist allocation failed.");
|
|
return ECMD_FAILED;
|
|
}
|
|
}
|
|
|
|
return ret_max;
|
|
}
|
|
|
|
static int _get_arg_devices(struct cmd_context *cmd,
|
|
struct dm_list *arg_pvnames,
|
|
struct dm_list *arg_devices)
|
|
{
|
|
struct dm_str_list *sl;
|
|
struct device_id_list *dil;
|
|
int ret_max = ECMD_PROCESSED;
|
|
|
|
dm_list_iterate_items(sl, arg_pvnames) {
|
|
if (!(dil = dm_pool_alloc(cmd->mem, sizeof(*dil)))) {
|
|
log_error("device_id_list alloc failed.");
|
|
return ECMD_FAILED;
|
|
}
|
|
|
|
if (!(dil->dev = dev_cache_get(sl->str, cmd->filter))) {
|
|
log_error("Failed to find device for physical volume \"%s\".", sl->str);
|
|
ret_max = ECMD_FAILED;
|
|
} else {
|
|
strncpy(dil->pvid, dil->dev->pvid, ID_LEN);
|
|
dm_list_add(arg_devices, &dil->list);
|
|
}
|
|
}
|
|
|
|
return ret_max;
|
|
}
|
|
|
|
static int _get_all_devices(struct cmd_context *cmd, struct dm_list *all_devices)
|
|
{
|
|
struct dev_iter *iter;
|
|
struct device *dev;
|
|
struct device_id_list *dil;
|
|
int r = ECMD_FAILED;
|
|
|
|
lvmcache_seed_infos_from_lvmetad(cmd);
|
|
|
|
if (!(iter = dev_iter_create(cmd->full_filter, 1))) {
|
|
log_error("dev_iter creation failed.");
|
|
return ECMD_FAILED;
|
|
}
|
|
|
|
while ((dev = dev_iter_get(iter))) {
|
|
if (!(dil = dm_pool_alloc(cmd->mem, sizeof(*dil)))) {
|
|
log_error("device_id_list alloc failed.");
|
|
goto out;
|
|
}
|
|
|
|
strncpy(dil->pvid, dev->pvid, ID_LEN);
|
|
dil->dev = dev;
|
|
dm_list_add(all_devices, &dil->list);
|
|
}
|
|
|
|
r = ECMD_PROCESSED;
|
|
out:
|
|
dev_iter_destroy(iter);
|
|
return r;
|
|
}
|
|
|
|
static int _device_list_remove(struct dm_list *devices, struct device *dev)
|
|
{
|
|
struct device_id_list *dil;
|
|
|
|
dm_list_iterate_items(dil, devices) {
|
|
if (dil->dev == dev) {
|
|
dm_list_del(&dil->list);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct device_id_list *_device_list_find_dev(struct dm_list *devices, struct device *dev)
|
|
{
|
|
struct device_id_list *dil;
|
|
|
|
dm_list_iterate_items(dil, devices) {
|
|
if (dil->dev == dev)
|
|
return dil;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct device_id_list *_device_list_find_pvid(struct dm_list *devices, struct physical_volume *pv)
|
|
{
|
|
struct device_id_list *dil;
|
|
|
|
dm_list_iterate_items(dil, devices) {
|
|
if (id_equal((struct id *) dil->pvid, &pv->id))
|
|
return dil;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int _process_device_list(struct cmd_context *cmd, struct dm_list *all_devices,
|
|
struct processing_handle *handle,
|
|
process_single_pv_fn_t process_single_pv)
|
|
{
|
|
struct physical_volume pv_dummy;
|
|
struct physical_volume *pv;
|
|
struct device_id_list *dil;
|
|
int ret_max = ECMD_PROCESSED;
|
|
int ret = 0;
|
|
|
|
/*
|
|
* Pretend that each device is a PV with dummy values.
|
|
* FIXME Formalise this extension or find an alternative.
|
|
*/
|
|
dm_list_iterate_items(dil, all_devices) {
|
|
if (sigint_caught())
|
|
return_ECMD_FAILED;
|
|
|
|
memset(&pv_dummy, 0, sizeof(pv_dummy));
|
|
dm_list_init(&pv_dummy.tags);
|
|
dm_list_init(&pv_dummy.segments);
|
|
pv_dummy.dev = dil->dev;
|
|
pv = &pv_dummy;
|
|
|
|
log_very_verbose("Processing device %s.", dev_name(dil->dev));
|
|
|
|
ret = process_single_pv(cmd, NULL, pv, handle);
|
|
|
|
if (ret > ret_max)
|
|
ret_max = ret;
|
|
}
|
|
|
|
return ECMD_PROCESSED;
|
|
}
|
|
|
|
static int _process_pvs_in_vg(struct cmd_context *cmd,
|
|
struct volume_group *vg,
|
|
struct dm_list *all_devices,
|
|
struct dm_list *arg_devices,
|
|
struct dm_list *arg_tags,
|
|
int process_all_pvs,
|
|
int process_all_devices,
|
|
int skip,
|
|
struct processing_handle *handle,
|
|
process_single_pv_fn_t process_single_pv)
|
|
{
|
|
int handle_supplied = handle != NULL;
|
|
struct physical_volume *pv;
|
|
struct pv_list *pvl;
|
|
struct device_id_list *dil;
|
|
struct device *dev_orig;
|
|
const char *pv_name;
|
|
int selected;
|
|
int process_pv;
|
|
int dev_found;
|
|
int ret_max = ECMD_PROCESSED;
|
|
int ret = 0;
|
|
|
|
if (!handle && (!(handle = init_processing_handle(cmd)))) {
|
|
ret_max = ECMD_FAILED;
|
|
goto_out;
|
|
}
|
|
|
|
if (handle->internal_report_for_select && !handle->selection_handle &&
|
|
!init_selection_handle(cmd, handle, PVS)) {
|
|
ret_max = ECMD_FAILED;
|
|
goto_out;
|
|
}
|
|
|
|
dm_list_iterate_items(pvl, &vg->pvs) {
|
|
if (sigint_caught()) {
|
|
ret_max = ECMD_FAILED;
|
|
goto_out;
|
|
}
|
|
|
|
pv = pvl->pv;
|
|
pv_name = pv_dev_name(pv);
|
|
|
|
process_pv = process_all_pvs;
|
|
|
|
/* Remove each arg_devices entry as it is processed. */
|
|
|
|
if (!process_pv && !dm_list_empty(arg_devices) &&
|
|
(dil = _device_list_find_dev(arg_devices, pv->dev))) {
|
|
process_pv = 1;
|
|
_device_list_remove(arg_devices, dil->dev);
|
|
}
|
|
|
|
/* Select the PV if the device arg has the same pvid. */
|
|
|
|
if (!process_pv && !dm_list_empty(arg_devices) &&
|
|
(dil = _device_list_find_pvid(arg_devices, pv))) {
|
|
process_pv = 1;
|
|
_device_list_remove(arg_devices, dil->dev);
|
|
}
|
|
|
|
if (!process_pv && !dm_list_empty(arg_tags) &&
|
|
str_list_match_list(arg_tags, &pv->tags, NULL))
|
|
process_pv = 1;
|
|
|
|
process_pv = process_pv && select_match_pv(cmd, handle, vg, pv, &selected) && selected;
|
|
|
|
if (process_pv) {
|
|
if (skip)
|
|
log_verbose("Skipping PV %s in VG %s.", pv_name, vg->name);
|
|
else
|
|
log_very_verbose("Processing PV %s in VG %s.", pv_name, vg->name);
|
|
|
|
dev_found = _device_list_remove(all_devices, pv->dev);
|
|
|
|
/*
|
|
* FIXME PVs with no mdas may turn up in an orphan VG when
|
|
* not using lvmetad as well as their correct VG. They
|
|
* will be missing from all_devices the second time
|
|
* around but must not be processed twice or trigger a message.
|
|
*
|
|
* Missing PVs will also need processing even though they are
|
|
* not present in all_devices.
|
|
*/
|
|
if (!dev_found && !is_missing_pv(pv)) {
|
|
log_verbose("Skipping PV %s in VG %s: not in device list.", pv_name, vg->name);
|
|
continue;
|
|
}
|
|
|
|
if (!skip) {
|
|
ret = process_single_pv(cmd, vg, pv, handle);
|
|
if (ret != ECMD_PROCESSED)
|
|
stack;
|
|
if (ret > ret_max)
|
|
ret_max = ret;
|
|
}
|
|
|
|
/*
|
|
* This is a very rare and obscure case where multiple
|
|
* duplicate devices are specified on the command line
|
|
* referring to this PV. In this case we want to
|
|
* process this PV once for each specified device.
|
|
*/
|
|
|
|
if (!skip && !dm_list_empty(arg_devices)) {
|
|
while ((dil = _device_list_find_pvid(arg_devices, pv))) {
|
|
_device_list_remove(arg_devices, dil->dev);
|
|
|
|
/*
|
|
* Replace pv->dev with this dil->dev
|
|
* in lvmcache so the duplicate dev
|
|
* info will be reported. FIXME: it
|
|
* would be nicer to override pv->dev
|
|
* without munging lvmcache content.
|
|
*/
|
|
dev_orig = pv->dev;
|
|
lvmcache_replace_dev(cmd, pv, dil->dev);
|
|
|
|
log_very_verbose("Processing PV %s device %s in VG %s.",
|
|
pv_name, dev_name(dil->dev), vg->name);
|
|
|
|
ret = process_single_pv(cmd, vg, pv, handle);
|
|
if (ret != ECMD_PROCESSED)
|
|
stack;
|
|
if (ret > ret_max)
|
|
ret_max = ret;
|
|
|
|
/* Put the cache state back as it was. */
|
|
lvmcache_replace_dev(cmd, pv, dev_orig);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This is another rare and obscure case where multiple
|
|
* duplicate devices are being displayed by pvs -a, and
|
|
* we want each of them to be displayed in the context
|
|
* of this VG, so that this VG name appears next to it.
|
|
*/
|
|
|
|
if (process_all_devices && lvmcache_found_duplicate_pvs()) {
|
|
while ((dil = _device_list_find_pvid(all_devices, pv))) {
|
|
_device_list_remove(all_devices, dil->dev);
|
|
|
|
dev_orig = pv->dev;
|
|
lvmcache_replace_dev(cmd, pv, dil->dev);
|
|
|
|
ret = process_single_pv(cmd, vg, pv, handle);
|
|
if (ret != ECMD_PROCESSED)
|
|
stack;
|
|
if (ret > ret_max)
|
|
ret_max = ret;
|
|
|
|
lvmcache_replace_dev(cmd, pv, dev_orig);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* When processing only specific PVs, we can quit once they've all been found.
|
|
*/
|
|
if (!process_all_pvs && dm_list_empty(arg_tags) && dm_list_empty(arg_devices))
|
|
break;
|
|
}
|
|
out:
|
|
if (!handle_supplied)
|
|
destroy_processing_handle(cmd, handle);
|
|
return ret_max;
|
|
}
|
|
|
|
/*
|
|
* Iterate through all PVs in each listed VG. Process a PV if
|
|
* its dev or tag matches arg_devices or arg_tags. If both
|
|
* arg_devices and arg_tags are empty, then process all PVs.
|
|
* No PV should be processed more than once.
|
|
*
|
|
* Each PV is removed from arg_devices and all_devices when it is
|
|
* processed. Any names remaining in arg_devices were not found, and
|
|
* should produce an error. Any devices remaining in all_devices were
|
|
* not found and should be processed by process_device_list().
|
|
*/
|
|
static int _process_pvs_in_vgs(struct cmd_context *cmd, uint32_t flags,
|
|
struct dm_list *all_vgnameids,
|
|
struct dm_list *all_devices,
|
|
struct dm_list *arg_devices,
|
|
struct dm_list *arg_tags,
|
|
int process_all_pvs,
|
|
int process_all_devices,
|
|
struct processing_handle *handle,
|
|
process_single_pv_fn_t process_single_pv)
|
|
{
|
|
struct volume_group *vg;
|
|
struct vgnameid_list *vgnl;
|
|
const char *vg_name;
|
|
const char *vg_uuid;
|
|
uint32_t lockd_state = 0;
|
|
int ret_max = ECMD_PROCESSED;
|
|
int ret;
|
|
int skip;
|
|
|
|
dm_list_iterate_items(vgnl, all_vgnameids) {
|
|
if (sigint_caught())
|
|
return_ECMD_FAILED;
|
|
|
|
vg_name = vgnl->vg_name;
|
|
vg_uuid = vgnl->vgid;
|
|
skip = 0;
|
|
|
|
if (!lockd_vg(cmd, vg_name, NULL, 0, &lockd_state)) {
|
|
ret_max = ECMD_FAILED;
|
|
continue;
|
|
}
|
|
|
|
vg = vg_read(cmd, vg_name, vg_uuid, flags | READ_WARN_INCONSISTENT, lockd_state);
|
|
if (_ignore_vg(vg, vg_name, NULL, flags & READ_ALLOW_INCONSISTENT, &skip)) {
|
|
stack;
|
|
ret_max = ECMD_FAILED;
|
|
if (!skip)
|
|
goto endvg;
|
|
/* Drop through to eliminate a clustered VG's PVs from the devices list */
|
|
}
|
|
|
|
/*
|
|
* Don't continue when skip is set, because we need to remove
|
|
* vg->pvs entries from devices list.
|
|
*/
|
|
|
|
ret = _process_pvs_in_vg(cmd, vg, all_devices, arg_devices, arg_tags,
|
|
process_all_pvs, process_all_devices, skip,
|
|
handle, process_single_pv);
|
|
if (ret != ECMD_PROCESSED)
|
|
stack;
|
|
if (ret > ret_max)
|
|
ret_max = ret;
|
|
|
|
if (!skip)
|
|
unlock_vg(cmd, vg->name);
|
|
endvg:
|
|
release_vg(vg);
|
|
if (!lockd_vg(cmd, vg_name, "un", 0, &lockd_state))
|
|
stack;
|
|
|
|
/* Quit early when possible. */
|
|
if (!process_all_pvs && dm_list_empty(arg_tags) && dm_list_empty(arg_devices))
|
|
return ret_max;
|
|
}
|
|
|
|
return ret_max;
|
|
}
|
|
|
|
int process_each_pv(struct cmd_context *cmd,
|
|
int argc, char **argv,
|
|
const char *only_this_vgname,
|
|
uint32_t flags,
|
|
struct processing_handle *handle,
|
|
process_single_pv_fn_t process_single_pv)
|
|
{
|
|
struct dm_list arg_tags; /* str_list */
|
|
struct dm_list arg_pvnames; /* str_list */
|
|
struct dm_list arg_devices; /* device_id_list */
|
|
struct dm_list all_vgnameids; /* vgnameid_list */
|
|
struct dm_list all_devices; /* device_id_list */
|
|
struct device_id_list *dil;
|
|
int process_all_pvs;
|
|
int process_all_devices;
|
|
int ret_max = ECMD_PROCESSED;
|
|
int ret;
|
|
|
|
/* Disable error in vg_read so we can print it from ignore_vg. */
|
|
cmd->vg_read_print_access_error = 0;
|
|
|
|
dm_list_init(&arg_tags);
|
|
dm_list_init(&arg_pvnames);
|
|
dm_list_init(&arg_devices);
|
|
dm_list_init(&all_vgnameids);
|
|
dm_list_init(&all_devices);
|
|
|
|
/*
|
|
* Create two lists from argv:
|
|
* arg_pvnames: pvs explicitly named in argv
|
|
* arg_tags: tags explicitly named in argv
|
|
*
|
|
* Then convert arg_pvnames, which are free-form, user-specified,
|
|
* names/paths into arg_devices which can be used to match below.
|
|
*/
|
|
if ((ret = _get_arg_pvnames(cmd, argc, argv, &arg_pvnames, &arg_tags)) != ECMD_PROCESSED) {
|
|
stack;
|
|
return ret;
|
|
}
|
|
|
|
process_all_pvs = dm_list_empty(&arg_pvnames) && dm_list_empty(&arg_tags);
|
|
|
|
process_all_devices = process_all_pvs && (cmd->command->flags & ENABLE_ALL_DEVS) &&
|
|
arg_count(cmd, all_ARG);
|
|
|
|
/* Needed for a current listing of the global VG namespace. */
|
|
if (!only_this_vgname && !lockd_gl(cmd, "sh", 0))
|
|
return_ECMD_FAILED;
|
|
|
|
/*
|
|
* Need pvid's set on all PVs before processing so that pvid's
|
|
* can be compared to find duplicates while processing.
|
|
*/
|
|
lvmcache_seed_infos_from_lvmetad(cmd);
|
|
|
|
if (!get_vgnameids(cmd, &all_vgnameids, only_this_vgname, 1)) {
|
|
stack;
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* If the caller wants to process all devices (not just PVs), then all PVs
|
|
* from all VGs are processed first, removing them from all_devices. Then
|
|
* any devs remaining in all_devices are processed.
|
|
*/
|
|
if ((ret = _get_all_devices(cmd, &all_devices) != ECMD_PROCESSED)) {
|
|
stack;
|
|
return ret;
|
|
}
|
|
|
|
if ((ret = _get_arg_devices(cmd, &arg_pvnames, &arg_devices) != ECMD_PROCESSED))
|
|
/* get_arg_devices reports the error for any PV names not found. */
|
|
ret_max = ECMD_FAILED;
|
|
|
|
ret = _process_pvs_in_vgs(cmd, flags, &all_vgnameids, &all_devices,
|
|
&arg_devices, &arg_tags,
|
|
process_all_pvs, process_all_devices,
|
|
handle, process_single_pv);
|
|
if (ret != ECMD_PROCESSED)
|
|
stack;
|
|
if (ret > ret_max)
|
|
ret_max = ret;
|
|
|
|
dm_list_iterate_items(dil, &arg_devices) {
|
|
log_error("Failed to find physical volume \"%s\".", dev_name(dil->dev));
|
|
ret_max = ECMD_FAILED;
|
|
}
|
|
|
|
if (!process_all_devices)
|
|
goto out;
|
|
|
|
ret = _process_device_list(cmd, &all_devices, handle, process_single_pv);
|
|
if (ret != ECMD_PROCESSED)
|
|
stack;
|
|
if (ret > ret_max)
|
|
ret_max = ret;
|
|
out:
|
|
return ret_max;
|
|
}
|
|
|
|
int process_each_pv_in_vg(struct cmd_context *cmd, struct volume_group *vg,
|
|
struct processing_handle *handle,
|
|
process_single_pv_fn_t process_single_pv)
|
|
{
|
|
int whole_selected = 0;
|
|
int ret_max = ECMD_PROCESSED;
|
|
int ret;
|
|
struct pv_list *pvl;
|
|
|
|
dm_list_iterate_items(pvl, &vg->pvs) {
|
|
if (sigint_caught())
|
|
return_ECMD_FAILED;
|
|
|
|
ret = process_single_pv(cmd, vg, pvl->pv, handle);
|
|
_update_selection_result(handle, &whole_selected);
|
|
if (ret != ECMD_PROCESSED)
|
|
stack;
|
|
if (ret > ret_max)
|
|
ret_max = ret;
|
|
}
|
|
|
|
_set_final_selection_result(handle, whole_selected);
|
|
return ret_max;
|
|
}
|
|
|
|
int lvremove_single(struct cmd_context *cmd, struct logical_volume *lv,
|
|
struct processing_handle *handle __attribute__((unused)))
|
|
{
|
|
/*
|
|
* Single force is equivalent to single --yes
|
|
* Even multiple --yes are equivalent to single --force
|
|
* When we require -ff it cannot be replaced with -f -y
|
|
*/
|
|
force_t force = (force_t) arg_count(cmd, force_ARG)
|
|
? : (arg_is_set(cmd, yes_ARG) ? DONT_PROMPT : PROMPT);
|
|
|
|
if (!lv_remove_with_dependencies(cmd, lv, force, 0))
|
|
return_ECMD_FAILED;
|
|
|
|
return ECMD_PROCESSED;
|
|
}
|