mirror of
git://sourceware.org/git/lvm2.git
synced 2025-01-03 05:18:29 +03:00
2208 lines
43 KiB
C
2208 lines
43 KiB
C
/*
|
|
* Copyright (C) 2002 Sistina Software (UK) Limited.
|
|
*
|
|
* This file is released under the LGPL.
|
|
*/
|
|
|
|
#include "lib.h"
|
|
#include "str_list.h"
|
|
#include "dev_manager.h"
|
|
#include "pool.h"
|
|
#include "hash.h"
|
|
#include "lvm-string.h"
|
|
#include "fs.h"
|
|
#include "defaults.h"
|
|
#include "toolcontext.h"
|
|
|
|
#include <libdevmapper.h>
|
|
#include <limits.h>
|
|
#include <dirent.h>
|
|
|
|
/*
|
|
* Algorithm
|
|
* ---------
|
|
*
|
|
* 1) Examine dm directory, and store details of active mapped devices
|
|
* in the VG. Indexed by lvid-layer. (_scan_existing_devices)
|
|
*
|
|
* 2) Build lists of visible devices that need to be left in each state:
|
|
* active, reloaded, suspended.
|
|
*
|
|
* 3) Run through these lists and set the appropriate marks on each device
|
|
* and its dependencies.
|
|
*
|
|
* 4) Add layers not marked active to remove_list for removal at the end.
|
|
*
|
|
* 5) Remove unmarked layers from core.
|
|
*
|
|
* 6) Activate remaining layers, recursing to handle dependedncies and
|
|
* skipping any that already exist unless they are marked as needing
|
|
* reloading.
|
|
*
|
|
* 7) Remove layers in the remove_list. (_remove_old_layers)
|
|
*
|
|
*/
|
|
|
|
#define MAX_TARGET_PARAMSIZE 50000
|
|
|
|
enum {
|
|
ACTIVE = 0,
|
|
RELOAD = 1,
|
|
VISIBLE = 2,
|
|
READWRITE = 3,
|
|
SUSPENDED = 4,
|
|
NOPROPAGATE = 5,
|
|
TOPLEVEL = 6,
|
|
REMOVE = 7
|
|
};
|
|
|
|
enum {
|
|
MIRR_DISABLED,
|
|
MIRR_RUNNING,
|
|
MIRR_COMPLETED
|
|
};
|
|
|
|
typedef enum {
|
|
ACTIVATE,
|
|
DEACTIVATE,
|
|
SUSPEND,
|
|
RESUME
|
|
} action_t;
|
|
|
|
struct dev_layer {
|
|
char *name;
|
|
|
|
int flags;
|
|
|
|
/*
|
|
* Setup the dm_task.
|
|
*/
|
|
int (*populate) (struct dev_manager * dm,
|
|
struct dm_task * dmt, struct dev_layer * dl);
|
|
struct dm_info info;
|
|
|
|
/* lvid plus layer */
|
|
const char *dlid;
|
|
|
|
struct logical_volume *lv;
|
|
|
|
/*
|
|
* Devices that must be created before this one can be created.
|
|
* Reloads get propagated to this list. Holds str_lists.
|
|
*/
|
|
struct list pre_create;
|
|
|
|
/* Inverse of pre_create */
|
|
struct list pre_suspend;
|
|
|
|
};
|
|
|
|
struct dl_list {
|
|
struct list list;
|
|
struct dev_layer *dl;
|
|
};
|
|
|
|
static const char *stripe_filler = NULL;
|
|
static uint32_t mirror_region_size = 0;
|
|
|
|
struct dev_manager {
|
|
struct pool *mem;
|
|
|
|
struct config_tree *cft;
|
|
const char *stripe_filler;
|
|
uint32_t mirror_region_size;
|
|
uint32_t pvmove_mirror_count;
|
|
|
|
char *vg_name;
|
|
|
|
/*
|
|
* list of struct lv_list, contains lvs that we wish to
|
|
* be active after execution.
|
|
*/
|
|
struct list active_list;
|
|
|
|
/*
|
|
* Layers that need reloading.
|
|
*/
|
|
struct list reload_list;
|
|
|
|
/*
|
|
* Layers that need suspending.
|
|
*/
|
|
struct list suspend_list;
|
|
|
|
/*
|
|
* Layers that will need removing after activation.
|
|
*/
|
|
struct list remove_list;
|
|
|
|
struct hash_table *layers;
|
|
};
|
|
|
|
/*
|
|
* Functions to manage the flags.
|
|
*/
|
|
static inline int _get_flag(struct dev_layer *dl, int bit)
|
|
{
|
|
return (dl->flags & (1 << bit)) ? 1 : 0;
|
|
}
|
|
|
|
static inline void _set_flag(struct dev_layer *dl, int bit)
|
|
{
|
|
dl->flags |= (1 << bit);
|
|
}
|
|
|
|
static inline void _clear_flag(struct dev_layer *dl, int bit)
|
|
{
|
|
dl->flags &= ~(1 << bit);
|
|
}
|
|
|
|
/*
|
|
* Device layer names are all of the form <vg>-<lv>-<layer>, any
|
|
* other hyphens that appear in these names are quoted with yet
|
|
* another hyphen. The top layer of any device has no layer
|
|
* name. eg, vg0-lvol0.
|
|
*/
|
|
static void _count_hyphens(const char *str, size_t *len, int *hyphens)
|
|
{
|
|
const char *ptr;
|
|
|
|
for (ptr = str; *ptr; ptr++, (*len)++)
|
|
if (*ptr == '-')
|
|
(*hyphens)++;
|
|
}
|
|
|
|
/*
|
|
* Copies a string, quoting hyphens with hyphens.
|
|
*/
|
|
static void _quote_hyphens(char **out, const char *src)
|
|
{
|
|
while (*src) {
|
|
if (*src == '-')
|
|
*(*out)++ = '-';
|
|
|
|
*(*out)++ = *src++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* <vg>-<lv>-<layer> or if !layer just <vg>-<lv>.
|
|
*/
|
|
static char *_build_name(struct pool *mem, const char *vg,
|
|
const char *lv, const char *layer)
|
|
{
|
|
size_t len = 0;
|
|
int hyphens = 0;
|
|
char *r, *out;
|
|
|
|
_count_hyphens(vg, &len, &hyphens);
|
|
_count_hyphens(lv, &len, &hyphens);
|
|
|
|
if (layer && *layer)
|
|
_count_hyphens(layer, &len, &hyphens);
|
|
|
|
len += hyphens + 2;
|
|
|
|
if (!(r = pool_alloc(mem, len))) {
|
|
stack;
|
|
return NULL;
|
|
}
|
|
|
|
out = r;
|
|
_quote_hyphens(&out, vg);
|
|
*out++ = '-';
|
|
_quote_hyphens(&out, lv);
|
|
|
|
if (layer && *layer) {
|
|
*out++ = '-';
|
|
_quote_hyphens(&out, layer);
|
|
}
|
|
*out = '\0';
|
|
|
|
return r;
|
|
}
|
|
|
|
/* Find start of LV component in hyphenated name */
|
|
static char *_find_lv_name(char *vg)
|
|
{
|
|
char *c = vg;
|
|
|
|
while (*c && *(c + 1)) {
|
|
if (*c == '-') {
|
|
if (*(c + 1) == '-')
|
|
c++;
|
|
else
|
|
return (c + 1);
|
|
}
|
|
c++;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static char *_build_dlid(struct pool *mem, const char *lvid, const char *layer)
|
|
{
|
|
char *dlid;
|
|
size_t len;
|
|
|
|
if (!layer)
|
|
layer = "";
|
|
|
|
len = strlen(lvid) + strlen(layer) + 2;
|
|
|
|
if (!(dlid = pool_alloc(mem, len))) {
|
|
stack;
|
|
return NULL;
|
|
}
|
|
|
|
sprintf(dlid, "%s%s%s", lvid, (*layer) ? "-" : "", layer);
|
|
|
|
return dlid;
|
|
}
|
|
|
|
/*
|
|
* Low level device-layer operations.
|
|
*/
|
|
static struct dm_task *_setup_task(const char *name, const char *uuid,
|
|
uint32_t *event_nr, int task)
|
|
{
|
|
struct dm_task *dmt;
|
|
|
|
if (!(dmt = dm_task_create(task))) {
|
|
stack;
|
|
return NULL;
|
|
}
|
|
|
|
if (name)
|
|
dm_task_set_name(dmt, name);
|
|
|
|
if (uuid && *uuid)
|
|
dm_task_set_uuid(dmt, uuid);
|
|
|
|
if (event_nr)
|
|
dm_task_set_event_nr(dmt, *event_nr);
|
|
|
|
return dmt;
|
|
}
|
|
|
|
static int _info_run(const char *name, const char *uuid, struct dm_info *info,
|
|
int mknodes, struct pool *mem, char **uuid_out)
|
|
{
|
|
int r = 0;
|
|
struct dm_task *dmt;
|
|
const char *u;
|
|
int dmtask;
|
|
|
|
dmtask = mknodes ? DM_DEVICE_MKNODES : DM_DEVICE_INFO;
|
|
|
|
if (!(dmt = _setup_task(name, uuid, 0, dmtask))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
if (!dm_task_run(dmt)) {
|
|
stack;
|
|
goto out;
|
|
}
|
|
|
|
if (!dm_task_get_info(dmt, info)) {
|
|
stack;
|
|
goto out;
|
|
}
|
|
|
|
if (info->exists && uuid_out) {
|
|
if (!(u = dm_task_get_uuid(dmt))) {
|
|
stack;
|
|
goto out;
|
|
}
|
|
*uuid_out = pool_strdup(mem, u);
|
|
}
|
|
r = 1;
|
|
|
|
out:
|
|
dm_task_destroy(dmt);
|
|
return r;
|
|
}
|
|
|
|
static int _info(const char *name, const char *uuid, int mknodes,
|
|
struct dm_info *info, struct pool *mem, char **uuid_out)
|
|
{
|
|
if (!mknodes && uuid && *uuid &&
|
|
_info_run(NULL, uuid, info, 0, mem, uuid_out) && info->exists)
|
|
return 1;
|
|
|
|
if (name)
|
|
return _info_run(name, NULL, info, mknodes, mem, uuid_out);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* FIXME Interface must cope with multiple targets */
|
|
static int _status_run(const char *name, const char *uuid,
|
|
unsigned long long *s, unsigned long long *l,
|
|
char **t, uint32_t t_size, char **p, uint32_t p_size)
|
|
{
|
|
int r = 0;
|
|
struct dm_task *dmt;
|
|
void *next = NULL;
|
|
uint64_t start, length;
|
|
char *type = NULL;
|
|
char *params = NULL;
|
|
|
|
if (!(dmt = _setup_task(name, uuid, 0, DM_DEVICE_STATUS))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
if (!dm_task_run(dmt)) {
|
|
stack;
|
|
goto out;
|
|
}
|
|
|
|
do {
|
|
next = dm_get_next_target(dmt, next, &start, &length,
|
|
&type, ¶ms);
|
|
if (type) {
|
|
*s = start;
|
|
*l = length;
|
|
/* Make sure things are null terminated */
|
|
strncpy(*t, type, t_size);
|
|
(*t)[t_size - 1] = '\0';
|
|
strncpy(*p, params, p_size);
|
|
(*p)[p_size - 1] = '\0';
|
|
|
|
r = 1;
|
|
/* FIXME Cope with multiple targets! */
|
|
break;
|
|
}
|
|
|
|
} while (next);
|
|
|
|
out:
|
|
dm_task_destroy(dmt);
|
|
return r;
|
|
}
|
|
|
|
static int _status(const char *name, const char *uuid,
|
|
unsigned long long *start, unsigned long long *length,
|
|
char **type, uint32_t type_size, char **params,
|
|
uint32_t param_size) __attribute__ ((unused));
|
|
|
|
static int _status(const char *name, const char *uuid,
|
|
unsigned long long *start, unsigned long long *length,
|
|
char **type, uint32_t type_size, char **params,
|
|
uint32_t param_size)
|
|
{
|
|
if (uuid && *uuid && _status_run(NULL, uuid, start, length, type,
|
|
type_size, params, param_size)
|
|
&& *params)
|
|
return 1;
|
|
|
|
if (name && _status_run(name, NULL, start, length, type, type_size,
|
|
params, param_size))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int _percent_run(struct dev_manager *dm, const char *name,
|
|
const char *uuid,
|
|
const char *target_type, int wait,
|
|
struct logical_volume *lv, float *percent,
|
|
uint32_t *event_nr)
|
|
{
|
|
int r = 0;
|
|
struct dm_task *dmt;
|
|
struct dm_info info;
|
|
void *next = NULL;
|
|
uint64_t start, length;
|
|
char *type = NULL;
|
|
char *params = NULL;
|
|
float percent2;
|
|
struct list *segh = &lv->segments;
|
|
struct lv_segment *seg = NULL;
|
|
|
|
uint64_t numerator, denominator;
|
|
uint64_t total_numerator = 0, total_denominator = 0;
|
|
|
|
*percent = -1;
|
|
|
|
if (!(dmt = _setup_task(name, uuid, event_nr,
|
|
wait ? DM_DEVICE_WAITEVENT : DM_DEVICE_STATUS)))
|
|
{
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
if (!dm_task_run(dmt)) {
|
|
stack;
|
|
goto out;
|
|
}
|
|
|
|
if (!dm_task_get_info(dmt, &info) || !info.exists) {
|
|
stack;
|
|
goto out;
|
|
}
|
|
|
|
if (event_nr)
|
|
*event_nr = info.event_nr;
|
|
|
|
do {
|
|
next = dm_get_next_target(dmt, next, &start, &length, &type,
|
|
¶ms);
|
|
if (lv) {
|
|
if (!(segh = list_next(&lv->segments, segh))) {
|
|
log_error("Number of segments in active LV %s "
|
|
"does not match metadata", lv->name);
|
|
goto out;
|
|
}
|
|
seg = list_item(segh, struct lv_segment);
|
|
}
|
|
|
|
if (!type || !params || strcmp(type, target_type))
|
|
continue;
|
|
|
|
/* Mirror? */
|
|
if (!strcmp(type, "mirror")) {
|
|
log_debug("Mirror status: %s", params);
|
|
if (sscanf(params, "%*d %*x:%*x %*x:%*x %" PRIu64
|
|
"/%" PRIu64, &numerator,
|
|
&denominator) != 2) {
|
|
log_error("Failure parsing mirror status: %s",
|
|
params);
|
|
goto out;
|
|
}
|
|
total_numerator += numerator;
|
|
total_denominator += denominator;
|
|
|
|
if (seg && (seg->status & PVMOVE))
|
|
seg->extents_moved = dm->mirror_region_size *
|
|
numerator / lv->vg->extent_size;
|
|
continue;
|
|
}
|
|
|
|
if (strcmp(type, "snapshot"))
|
|
continue;
|
|
|
|
/* Snapshot */
|
|
if (index(params, '/')) {
|
|
if (sscanf(params, "%" PRIu64 "/%" PRIu64,
|
|
&numerator, &denominator) == 2) {
|
|
total_numerator += numerator;
|
|
total_denominator += denominator;
|
|
}
|
|
continue;
|
|
} else if (sscanf(params, "%f", &percent2) == 1) {
|
|
*percent += percent2;
|
|
*percent /= 2;
|
|
}
|
|
} while (next);
|
|
|
|
if (lv && (segh = list_next(&lv->segments, segh))) {
|
|
log_error("Number of segments in active LV %s does not "
|
|
"match metadata", lv->name);
|
|
goto out;
|
|
}
|
|
|
|
if (total_denominator)
|
|
*percent = (float) total_numerator *100 / total_denominator;
|
|
else
|
|
*percent = 100;
|
|
|
|
log_debug("LV percent: %f", *percent);
|
|
r = 1;
|
|
|
|
out:
|
|
dm_task_destroy(dmt);
|
|
return r;
|
|
}
|
|
|
|
static int _percent(struct dev_manager *dm, const char *name, const char *uuid,
|
|
const char *target_type, int wait,
|
|
struct logical_volume *lv, float *percent,
|
|
uint32_t *event_nr)
|
|
{
|
|
if (uuid && *uuid
|
|
&& _percent_run(dm, NULL, uuid, target_type, wait, lv, percent,
|
|
event_nr))
|
|
return 1;
|
|
|
|
if (name && _percent_run(dm, name, NULL, target_type, wait, lv, percent,
|
|
event_nr))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int _rename(struct dev_layer *dl, char *newname)
|
|
{
|
|
int r = 1;
|
|
struct dm_task *dmt;
|
|
|
|
log_verbose("Renaming %s to %s", dl->name, newname);
|
|
|
|
if (!(dmt = _setup_task(dl->name, NULL, 0, DM_DEVICE_RENAME))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
if (!dm_task_set_newname(dmt, newname)) {
|
|
stack;
|
|
r = 0;
|
|
goto out;
|
|
}
|
|
|
|
if (!(r = dm_task_run(dmt)))
|
|
log_error("Couldn't rename device '%s'.", dl->name);
|
|
|
|
if (r && _get_flag(dl, VISIBLE))
|
|
fs_rename_lv(dl->lv, newname, _find_lv_name(dl->name));
|
|
|
|
dl->name = newname;
|
|
|
|
out:
|
|
dm_task_destroy(dmt);
|
|
return r;
|
|
}
|
|
|
|
static int _load(struct dev_manager *dm, struct dev_layer *dl, int task)
|
|
{
|
|
int r = 1;
|
|
struct dm_task *dmt;
|
|
|
|
log_verbose("Loading %s", dl->name);
|
|
if (!(dmt = _setup_task(task == DM_DEVICE_CREATE ? dl->name : NULL,
|
|
dl->dlid, 0, task))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Populate the table.
|
|
*/
|
|
if (!dl->populate(dm, dmt, dl)) {
|
|
log_error("Couldn't populate device '%s'.", dl->name);
|
|
r = 0;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Do we want a specific device number ?
|
|
*/
|
|
if (dl->lv->major >= 0 && _get_flag(dl, VISIBLE)) {
|
|
if (!dm_task_set_major(dmt, dl->lv->major)) {
|
|
log_error("Failed to set major number for %s to %d "
|
|
"during activation.", dl->name,
|
|
dl->lv->major);
|
|
goto out;
|
|
} else
|
|
log_very_verbose("Set major number for %s to %d.",
|
|
dl->name, dl->lv->major);
|
|
}
|
|
|
|
if (dl->lv->minor >= 0 && _get_flag(dl, VISIBLE)) {
|
|
if (!dm_task_set_minor(dmt, dl->lv->minor)) {
|
|
log_error("Failed to set minor number for %s to %d "
|
|
"during activation.", dl->name,
|
|
dl->lv->minor);
|
|
goto out;
|
|
} else
|
|
log_very_verbose("Set minor number for %s to %d.",
|
|
dl->name, dl->lv->minor);
|
|
}
|
|
|
|
if (!_get_flag(dl, READWRITE)) {
|
|
if (!dm_task_set_ro(dmt)) {
|
|
log_error("Failed to set %s read-only during "
|
|
"activation.", dl->name);
|
|
goto out;
|
|
} else
|
|
log_very_verbose("Activating %s read-only", dl->name);
|
|
}
|
|
|
|
if (!(r = dm_task_run(dmt))) {
|
|
log_error("Couldn't load device '%s'.", dl->name);
|
|
if ((dl->lv->minor >= 0 || dl->lv->major >= 0) &&
|
|
_get_flag(dl, VISIBLE))
|
|
log_error("Perhaps the persistent device number "
|
|
"%d:%d is already in use?",
|
|
dl->lv->major, dl->lv->minor);
|
|
}
|
|
|
|
if (!dm_task_get_info(dmt, &dl->info)) {
|
|
stack;
|
|
r = 0;
|
|
goto out;
|
|
}
|
|
|
|
if (!dl->info.exists || !dl->info.live_table) {
|
|
stack;
|
|
r = 0;
|
|
goto out;
|
|
}
|
|
|
|
log_very_verbose("Activated %s %s %03u:%03u", dl->name,
|
|
dl->dlid, dl->info.major, dl->info.minor);
|
|
|
|
if (r && _get_flag(dl, VISIBLE))
|
|
fs_add_lv(dl->lv, dl->name);
|
|
|
|
_clear_flag(dl, RELOAD);
|
|
|
|
out:
|
|
dm_task_destroy(dmt);
|
|
return r;
|
|
}
|
|
|
|
static int _remove(struct dev_layer *dl)
|
|
{
|
|
int r;
|
|
struct dm_task *dmt;
|
|
|
|
if (_get_flag(dl, VISIBLE))
|
|
log_verbose("Removing %s", dl->name);
|
|
else
|
|
log_very_verbose("Removing %s", dl->name);
|
|
|
|
if (!(dmt = _setup_task(dl->name, NULL, 0, DM_DEVICE_REMOVE))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
/* Suppress error message if it's still in use - we'll log it later */
|
|
log_suppress(1);
|
|
|
|
if ((r = dm_task_run(dmt)))
|
|
dl->info.exists = 0;
|
|
|
|
log_suppress(0);
|
|
|
|
dm_task_destroy(dmt);
|
|
|
|
if (r && _get_flag(dl, VISIBLE))
|
|
fs_del_lv(dl->lv);
|
|
|
|
_clear_flag(dl, ACTIVE);
|
|
|
|
return r;
|
|
}
|
|
|
|
static int _suspend_or_resume(const char *name, action_t suspend)
|
|
{
|
|
int r;
|
|
struct dm_task *dmt;
|
|
int sus = (suspend == SUSPEND) ? 1 : 0;
|
|
int task = sus ? DM_DEVICE_SUSPEND : DM_DEVICE_RESUME;
|
|
|
|
log_very_verbose("%s %s", sus ? "Suspending" : "Resuming", name);
|
|
if (!(dmt = _setup_task(name, NULL, 0, task))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
if (!(r = dm_task_run(dmt)))
|
|
log_error("Couldn't %s device '%s'", sus ? "suspend" : "resume",
|
|
name);
|
|
|
|
dm_task_destroy(dmt);
|
|
return r;
|
|
}
|
|
|
|
static int _suspend(struct dev_layer *dl)
|
|
{
|
|
if (!dl->info.exists || dl->info.suspended)
|
|
return 1;
|
|
|
|
if (!_suspend_or_resume(dl->name, SUSPEND)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
dl->info.suspended = 1;
|
|
return 1;
|
|
}
|
|
|
|
static int _resume(struct dev_layer *dl)
|
|
{
|
|
if (!dl->info.exists || !dl->info.suspended)
|
|
return 1;
|
|
|
|
if (!_suspend_or_resume(dl->name, RESUME)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
dl->info.suspended = 0;
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* The functions that populate the table in a dm_task as part of
|
|
* a create/reload.
|
|
*/
|
|
|
|
/*
|
|
* Emit a target for a given segment.
|
|
* FIXME: tidy this function.
|
|
*/
|
|
static int _emit_target_line(struct dev_manager *dm, struct dm_task *dmt,
|
|
struct lv_segment *seg, char *params,
|
|
size_t paramsize)
|
|
{
|
|
uint64_t esize = seg->lv->vg->extent_size;
|
|
uint32_t s, start_area = 0u, areas = seg->area_count;
|
|
int w = 0, tw = 0;
|
|
const char *target = NULL;
|
|
const char *trailing_space;
|
|
int mirror_status;
|
|
struct dev_layer *dl;
|
|
char devbuf[10];
|
|
|
|
switch (seg->type) {
|
|
case SEG_SNAPSHOT:
|
|
log_error("_emit_target: Internal error: Can't handle "
|
|
"SEG_SNAPSHOT");
|
|
return 0;
|
|
/* Target formats:
|
|
* linear [device offset]+
|
|
* striped #stripes stripe_size [device offset]+
|
|
* mirror log_type #log_params [log_params]*
|
|
* #mirrors [device offset]+
|
|
*/
|
|
case SEG_STRIPED:
|
|
if (areas == 1)
|
|
target = "linear";
|
|
else if (areas > 1) {
|
|
target = "striped";
|
|
if ((tw = lvm_snprintf(params, paramsize, "%u %u ",
|
|
areas, seg->stripe_size)) < 0)
|
|
goto error;
|
|
w = tw;
|
|
} else {
|
|
log_error("_emit_target: Internal error: SEG_STRIPED "
|
|
"with no stripes");
|
|
return 0;
|
|
}
|
|
break;
|
|
case SEG_MIRRORED:
|
|
mirror_status = MIRR_RUNNING;
|
|
if (seg->status & PVMOVE) {
|
|
if (seg->extents_moved == seg->area_len) {
|
|
mirror_status = MIRR_COMPLETED;
|
|
start_area = 1;
|
|
} else if (dm->pvmove_mirror_count++) {
|
|
mirror_status = MIRR_DISABLED;
|
|
areas = 1;
|
|
}
|
|
}
|
|
if (mirror_status != MIRR_RUNNING) {
|
|
target = "linear";
|
|
break;
|
|
}
|
|
target = "mirror";
|
|
if ((tw = lvm_snprintf(params, paramsize, "core 1 %u %u ",
|
|
dm->mirror_region_size, areas)) < 0)
|
|
goto error;
|
|
w = tw;
|
|
break;
|
|
}
|
|
|
|
for (s = start_area; s < areas; s++, w += tw) {
|
|
trailing_space = (areas - s - 1) ? " " : "";
|
|
if ((seg->area[s].type == AREA_PV &&
|
|
(!seg->area[s].u.pv.pv || !seg->area[s].u.pv.pv->dev)) ||
|
|
(seg->area[s].type == AREA_LV && !seg->area[s].u.lv.lv))
|
|
tw = lvm_snprintf(params + w, paramsize - w,
|
|
"%s 0%s", dm->stripe_filler,
|
|
trailing_space);
|
|
else if (seg->area[s].type == AREA_PV)
|
|
tw = lvm_snprintf(params + w, paramsize - w,
|
|
"%s %" PRIu64 "%s",
|
|
dev_name(seg->area[s].u.pv.pv->dev),
|
|
(seg->area[s].u.pv.pv->pe_start +
|
|
(esize * seg->area[s].u.pv.pe)),
|
|
trailing_space);
|
|
else {
|
|
if (!(dl = hash_lookup(dm->layers,
|
|
seg->area[s].u.lv.lv->lvid.s))) {
|
|
log_error("device layer %s missing from hash",
|
|
seg->area[s].u.lv.lv->lvid.s);
|
|
return 0;
|
|
}
|
|
if (!dm_format_dev(devbuf, sizeof(devbuf), dl->info.major, dl->info.minor)) {
|
|
log_error("Failed to format device number as dm target (%u,%u)",
|
|
dl->info.major, dl->info.minor);
|
|
return 0;
|
|
}
|
|
tw = lvm_snprintf(params + w, paramsize - w,
|
|
"%s %" PRIu64 "%s", devbuf,
|
|
esize * seg->area[s].u.lv.le,
|
|
trailing_space);
|
|
}
|
|
|
|
if (tw < 0)
|
|
goto error;
|
|
}
|
|
|
|
log_debug("Adding target: %" PRIu64 " %" PRIu64 " %s %s",
|
|
esize * seg->le, esize * seg->len, target, params);
|
|
|
|
if (!dm_task_add_target(dmt, esize * seg->le, esize * seg->len,
|
|
target, params)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
|
|
error:
|
|
log_debug("Insufficient space in params[%" PRIsize_t "] for target "
|
|
"parameters.", paramsize);
|
|
return -1;
|
|
}
|
|
|
|
static int _emit_target(struct dev_manager *dm, struct dm_task *dmt,
|
|
struct lv_segment *seg)
|
|
{
|
|
char *params;
|
|
size_t paramsize = 4096;
|
|
int ret;
|
|
|
|
do {
|
|
if (!(params = dbg_malloc(paramsize))) {
|
|
log_error("Insufficient space for target parameters.");
|
|
return 0;
|
|
}
|
|
|
|
ret = _emit_target_line(dm, dmt, seg, params, paramsize);
|
|
dbg_free(params);
|
|
|
|
if (!ret)
|
|
stack;
|
|
|
|
if (ret >= 0)
|
|
return ret;
|
|
|
|
paramsize *= 2;
|
|
} while (paramsize < MAX_TARGET_PARAMSIZE);
|
|
|
|
log_error("Target parameter size too big. Aborting.");
|
|
return 0;
|
|
}
|
|
|
|
static int _populate_vanilla(struct dev_manager *dm,
|
|
struct dm_task *dmt, struct dev_layer *dl)
|
|
{
|
|
struct list *segh;
|
|
struct lv_segment *seg;
|
|
struct logical_volume *lv = dl->lv;
|
|
|
|
dm->pvmove_mirror_count = 0u;
|
|
|
|
list_iterate(segh, &lv->segments) {
|
|
seg = list_item(segh, struct lv_segment);
|
|
if (!_emit_target(dm, dmt, seg)) {
|
|
log_error("Unable to build table for '%s'", lv->name);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int _populate_origin(struct dev_manager *dm,
|
|
struct dm_task *dmt, struct dev_layer *dl)
|
|
{
|
|
char *real;
|
|
char params[PATH_MAX + 32];
|
|
struct dev_layer *dlr;
|
|
|
|
if (!(real = _build_dlid(dm->mem, dl->lv->lvid.s, "real"))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
if (!(dlr = hash_lookup(dm->layers, real))) {
|
|
log_error("Couldn't find real device layer %s in hash", real);
|
|
return 0;
|
|
}
|
|
|
|
if (!dm_format_dev(params, sizeof(params), dlr->info.major,
|
|
dlr->info.minor)) {
|
|
log_error("Couldn't create origin device parameters for '%s'.",
|
|
real);
|
|
return 0;
|
|
}
|
|
|
|
log_debug("Adding target: 0 %" PRIu64 " snapshot-origin %s",
|
|
dl->lv->size, params);
|
|
if (!dm_task_add_target(dmt, UINT64_C(0), dl->lv->size,
|
|
"snapshot-origin", params)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int _populate_snapshot(struct dev_manager *dm,
|
|
struct dm_task *dmt, struct dev_layer *dl)
|
|
{
|
|
char *origin, *cow;
|
|
char params[PATH_MAX * 2 + 32];
|
|
struct snapshot *s;
|
|
struct dev_layer *dlo, *dlc;
|
|
char devbufo[10], devbufc[10];
|
|
|
|
if (!(s = find_cow(dl->lv))) {
|
|
log_error("Couldn't find snapshot for '%s'.", dl->lv->name);
|
|
return 0;
|
|
}
|
|
|
|
if (!(origin = _build_dlid(dm->mem, s->origin->lvid.s, "real"))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
if (!(cow = _build_dlid(dm->mem, s->cow->lvid.s, "cow"))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
if (!(dlo = hash_lookup(dm->layers, origin))) {
|
|
log_error("Couldn't find origin device layer %s in hash",
|
|
origin);
|
|
return 0;
|
|
}
|
|
|
|
if (!(dlc = hash_lookup(dm->layers, cow))) {
|
|
log_error("Couldn't find cow device layer %s in hash", cow);
|
|
return 0;
|
|
}
|
|
|
|
if (!dm_format_dev(devbufo, sizeof(devbufo), dlo->info.major,
|
|
dlo->info.minor)) {
|
|
log_error("Couldn't create origin device parameters for '%s'.",
|
|
s->origin->name);
|
|
return 0;
|
|
}
|
|
|
|
if (!dm_format_dev(devbufc, sizeof(devbufc), dlc->info.major,
|
|
dlc->info.minor)) {
|
|
log_error("Couldn't create cow device parameters for '%s'.",
|
|
s->cow->name);
|
|
return 0;
|
|
}
|
|
|
|
if (lvm_snprintf(params, sizeof(params), "%s %s P %d",
|
|
devbufo, devbufc, s->chunk_size) == -1) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
log_debug("Adding target: 0 %" PRIu64 " snapshot %s",
|
|
s->origin->size, params);
|
|
if (!dm_task_add_target
|
|
(dmt, UINT64_C(0), s->origin->size, "snapshot", params)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* dev_manager implementation.
|
|
*/
|
|
struct dev_manager *dev_manager_create(const char *vg_name,
|
|
struct config_tree *cft)
|
|
{
|
|
struct pool *mem;
|
|
struct dev_manager *dm;
|
|
|
|
if (!(mem = pool_create(16 * 1024))) {
|
|
stack;
|
|
return NULL;
|
|
}
|
|
|
|
if (!(dm = pool_alloc(mem, sizeof(*dm)))) {
|
|
stack;
|
|
goto bad;
|
|
}
|
|
|
|
dm->mem = mem;
|
|
dm->cft = cft;
|
|
if (!stripe_filler) {
|
|
stripe_filler = find_config_str(cft->root,
|
|
"activation/missing_stripe_filler",
|
|
DEFAULT_STRIPE_FILLER);
|
|
}
|
|
dm->stripe_filler = stripe_filler;
|
|
|
|
if (!mirror_region_size) {
|
|
mirror_region_size = 2 * find_config_int(cft->root,
|
|
"activation/mirror_region_size",
|
|
DEFAULT_MIRROR_REGION_SIZE);
|
|
}
|
|
dm->mirror_region_size = mirror_region_size;
|
|
|
|
if (!(dm->vg_name = pool_strdup(dm->mem, vg_name))) {
|
|
stack;
|
|
goto bad;
|
|
}
|
|
|
|
if (!(dm->layers = hash_create(32))) {
|
|
stack;
|
|
goto bad;
|
|
}
|
|
|
|
list_init(&dm->active_list);
|
|
list_init(&dm->reload_list);
|
|
list_init(&dm->remove_list);
|
|
list_init(&dm->suspend_list);
|
|
|
|
return dm;
|
|
|
|
bad:
|
|
pool_destroy(mem);
|
|
return NULL;
|
|
}
|
|
|
|
void dev_manager_destroy(struct dev_manager *dm)
|
|
{
|
|
hash_destroy(dm->layers);
|
|
pool_destroy(dm->mem);
|
|
}
|
|
|
|
int dev_manager_info(struct dev_manager *dm, const struct logical_volume *lv,
|
|
int mknodes, struct dm_info *info)
|
|
{
|
|
char *name;
|
|
|
|
/*
|
|
* Build a name for the top layer.
|
|
*/
|
|
if (!(name = _build_name(dm->mem, lv->vg->name, lv->name, NULL))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Try and get some info on this device.
|
|
*/
|
|
log_debug("Getting device info for %s", name);
|
|
if (!_info(name, lv->lvid.s, mknodes, info, NULL, NULL)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int dev_manager_snapshot_percent(struct dev_manager *dm,
|
|
struct logical_volume *lv, float *percent)
|
|
{
|
|
char *name;
|
|
|
|
/*
|
|
* Build a name for the top layer.
|
|
*/
|
|
if (!(name = _build_name(dm->mem, lv->vg->name, lv->name, NULL))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Try and get some info on this device.
|
|
*/
|
|
log_debug("Getting device status percentage for %s", name);
|
|
if (!(_percent(dm, name, lv->lvid.s, "snapshot", 0, NULL, percent,
|
|
NULL))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
/* FIXME pool_free ? */
|
|
|
|
/* If the snapshot isn't available, percent will be -1 */
|
|
return 1;
|
|
}
|
|
|
|
/* FIXME Merge with snapshot_percent, auto-detecting target type */
|
|
/* FIXME Cope with more than one target */
|
|
int dev_manager_mirror_percent(struct dev_manager *dm,
|
|
struct logical_volume *lv, int wait,
|
|
float *percent, uint32_t *event_nr)
|
|
{
|
|
char *name;
|
|
|
|
/*
|
|
* Build a name for the top layer.
|
|
*/
|
|
if (!(name = _build_name(dm->mem, lv->vg->name, lv->name, NULL))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
/* FIXME pool_free ? */
|
|
|
|
log_debug("Getting device mirror status percentage for %s", name);
|
|
if (!(_percent(dm, name, lv->lvid.s, "mirror", wait, lv, percent,
|
|
event_nr))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static struct dev_layer *_create_dev(struct dev_manager *dm, char *name,
|
|
const char *dlid)
|
|
{
|
|
struct dev_layer *dl;
|
|
char *uuid;
|
|
|
|
if (!(dl = pool_zalloc(dm->mem, sizeof(*dl)))) {
|
|
stack;
|
|
return NULL;
|
|
}
|
|
|
|
dl->name = name;
|
|
|
|
log_debug("Getting device info for %s", dl->name);
|
|
if (!_info(dl->name, dlid, 0, &dl->info, dm->mem, &uuid)) {
|
|
stack;
|
|
return NULL;
|
|
}
|
|
|
|
if (dl->info.exists)
|
|
dl->dlid = uuid;
|
|
else
|
|
dl->dlid = dlid;
|
|
|
|
list_init(&dl->pre_create);
|
|
list_init(&dl->pre_suspend);
|
|
|
|
if (!hash_insert(dm->layers, dl->dlid, dl)) {
|
|
stack;
|
|
return NULL;
|
|
}
|
|
|
|
return dl;
|
|
}
|
|
|
|
static inline int _read_only_lv(struct logical_volume *lv)
|
|
{
|
|
return (!(lv->vg->status & LVM_WRITE) || !(lv->status & LVM_WRITE));
|
|
}
|
|
|
|
static struct dev_layer *_create_layer(struct dev_manager *dm,
|
|
const char *layer,
|
|
struct logical_volume *lv)
|
|
{
|
|
char *name, *dlid;
|
|
struct dev_layer *dl;
|
|
|
|
if (!(name = _build_name(dm->mem, lv->vg->name, lv->name, layer))) {
|
|
stack;
|
|
return NULL;
|
|
}
|
|
|
|
if (!(dlid = _build_dlid(dm->mem, lv->lvid.s, layer))) {
|
|
stack;
|
|
return NULL;
|
|
}
|
|
|
|
if (!(dl = hash_lookup(dm->layers, dlid)) &&
|
|
!(dl = _create_dev(dm, name, dlid))) {
|
|
stack;
|
|
return NULL;
|
|
}
|
|
|
|
dl->lv = lv;
|
|
|
|
if (!_read_only_lv(lv))
|
|
_set_flag(dl, READWRITE);
|
|
|
|
return dl;
|
|
}
|
|
|
|
/*
|
|
* Finds the specified layer.
|
|
*/
|
|
static struct dev_layer *_lookup(struct dev_manager *dm,
|
|
const char *lvid, const char *layer)
|
|
{
|
|
char *dlid;
|
|
struct dev_layer *dl;
|
|
|
|
if (!(dlid = _build_dlid(dm->mem, lvid, layer))) {
|
|
stack;
|
|
return NULL;
|
|
}
|
|
|
|
dl = hash_lookup(dm->layers, dlid);
|
|
pool_free(dm->mem, dlid);
|
|
return dl;
|
|
}
|
|
|
|
static int _expand_vanilla(struct dev_manager *dm, struct logical_volume *lv,
|
|
int was_origin)
|
|
{
|
|
/*
|
|
* only one layer.
|
|
*/
|
|
struct dev_layer *dl, *dlr;
|
|
struct list *segh;
|
|
struct lv_segment *seg;
|
|
uint32_t s;
|
|
|
|
if (!(dl = _create_layer(dm, NULL, lv))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
dl->populate = _populate_vanilla;
|
|
if (lv->status & VISIBLE_LV) {
|
|
_set_flag(dl, VISIBLE);
|
|
_set_flag(dl, TOPLEVEL);
|
|
}
|
|
|
|
if (lv->status & PVMOVE)
|
|
_set_flag(dl, TOPLEVEL);
|
|
|
|
/* Add dependencies for any LVs that segments refer to */
|
|
list_iterate(segh, &lv->segments) {
|
|
seg = list_item(segh, struct lv_segment);
|
|
if (seg->type != SEG_STRIPED && seg->type != SEG_MIRRORED)
|
|
continue;
|
|
for (s = 0; s < seg->area_count; s++) {
|
|
if (seg->area[s].type != AREA_LV)
|
|
continue;
|
|
if (!str_list_add(dm->mem, &dl->pre_create,
|
|
_build_dlid(dm->mem,
|
|
seg->area[s].u.lv.
|
|
lv->lvid.s, NULL))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
_set_flag(dl, NOPROPAGATE);
|
|
}
|
|
}
|
|
|
|
if (!was_origin)
|
|
return 1;
|
|
|
|
/* Deactivating the last snapshot */
|
|
if (!(dlr = _create_layer(dm, "real", lv))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
dlr->populate = _populate_vanilla;
|
|
_clear_flag(dlr, VISIBLE);
|
|
_clear_flag(dlr, TOPLEVEL);
|
|
_set_flag(dlr, REMOVE);
|
|
|
|
/* add the dependency on the real device */
|
|
if (!str_list_add(dm->mem, &dl->pre_create,
|
|
pool_strdup(dm->mem, dlr->dlid))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int _expand_origin_real(struct dev_manager *dm,
|
|
struct logical_volume *lv)
|
|
{
|
|
struct dev_layer *dl;
|
|
const char *real_dlid;
|
|
|
|
if (!(dl = _create_layer(dm, "real", lv))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
dl->populate = _populate_vanilla;
|
|
_clear_flag(dl, VISIBLE);
|
|
_clear_flag(dl, TOPLEVEL);
|
|
|
|
real_dlid = dl->dlid;
|
|
|
|
if (!(dl = _create_layer(dm, NULL, lv))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
dl->populate = _populate_origin;
|
|
_set_flag(dl, VISIBLE);
|
|
_set_flag(dl, TOPLEVEL);
|
|
|
|
/* add the dependency on the real device */
|
|
if (!str_list_add(dm->mem, &dl->pre_create,
|
|
pool_strdup(dm->mem, real_dlid))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int _expand_origin(struct dev_manager *dm, struct logical_volume *lv)
|
|
{
|
|
struct logical_volume *active;
|
|
struct snapshot *s;
|
|
struct list *sh;
|
|
|
|
/*
|
|
* We only need to create an origin layer if one of our
|
|
* snapshots is in the active list
|
|
*/
|
|
list_iterate(sh, &dm->active_list) {
|
|
active = list_item(sh, struct lv_list)->lv;
|
|
if ((s = find_cow(active)) && (s->origin == lv))
|
|
return _expand_origin_real(dm, lv);
|
|
}
|
|
|
|
/*
|
|
* We're deactivating the last snapshot
|
|
*/
|
|
return _expand_vanilla(dm, lv, 1);
|
|
}
|
|
|
|
static int _expand_snapshot(struct dev_manager *dm, struct logical_volume *lv,
|
|
struct snapshot *s)
|
|
{
|
|
/*
|
|
* snapshot(org, cow)
|
|
* cow
|
|
*/
|
|
struct dev_layer *dl;
|
|
const char *cow_dlid;
|
|
|
|
if (!(dl = _create_layer(dm, "cow", lv))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
dl->populate = _populate_vanilla;
|
|
_clear_flag(dl, VISIBLE);
|
|
_clear_flag(dl, TOPLEVEL);
|
|
_set_flag(dl, READWRITE);
|
|
|
|
cow_dlid = dl->dlid;
|
|
|
|
if (!(dl = _create_layer(dm, NULL, lv))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
dl->populate = _populate_snapshot;
|
|
_set_flag(dl, VISIBLE);
|
|
_set_flag(dl, TOPLEVEL);
|
|
|
|
/* add the dependency on the cow device */
|
|
if (!str_list_add(dm->mem, &dl->pre_create,
|
|
pool_strdup(dm->mem, cow_dlid))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
/* add the dependency on the real origin device */
|
|
if (!str_list_add(dm->mem, &dl->pre_create,
|
|
_build_dlid(dm->mem, s->origin->lvid.s, "real"))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
/* add the dependency on the visible origin device */
|
|
if (!str_list_add(dm->mem, &dl->pre_suspend, s->origin->lvid.s)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Inserts the appropriate dev_layers for a logical volume.
|
|
*/
|
|
static int _expand_lv(struct dev_manager *dm, struct logical_volume *lv)
|
|
{
|
|
struct snapshot *s;
|
|
|
|
/*
|
|
* FIXME: this doesn't cope with recursive snapshots yet.
|
|
*/
|
|
if ((s = find_cow(lv)))
|
|
return _expand_snapshot(dm, lv, s);
|
|
|
|
else if (lv_is_origin(lv))
|
|
return _expand_origin(dm, lv);
|
|
|
|
return _expand_vanilla(dm, lv, 0);
|
|
}
|
|
|
|
/*
|
|
* Clears the mark bit on all layers.
|
|
*/
|
|
static void _clear_marks(struct dev_manager *dm, int flag)
|
|
{
|
|
struct hash_node *hn;
|
|
struct dev_layer *dl;
|
|
|
|
hash_iterate(hn, dm->layers) {
|
|
dl = hash_get_data(dm->layers, hn);
|
|
_clear_flag(dl, flag);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Propogates marks via the pre_create dependency list.
|
|
*/
|
|
static int _trace_layer_marks(struct dev_manager *dm, struct dev_layer *dl,
|
|
int flag)
|
|
{
|
|
struct list *sh;
|
|
const char *dlid;
|
|
struct dev_layer *dep;
|
|
|
|
list_iterate(sh, &dl->pre_create) {
|
|
dlid = list_item(sh, struct str_list)->str;
|
|
|
|
if (!(dep = hash_lookup(dm->layers, dlid))) {
|
|
log_error("Couldn't find device layer '%s'.", dlid);
|
|
return 0;
|
|
}
|
|
|
|
if (_get_flag(dep, flag))
|
|
continue;
|
|
|
|
/* FIXME Only propagate LV ACTIVE dependencies for now */
|
|
if ((flag != ACTIVE) && _get_flag(dl, NOPROPAGATE))
|
|
continue;
|
|
|
|
_set_flag(dep, flag);
|
|
|
|
if (!_trace_layer_marks(dm, dep, flag)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Calls _trace_single for every marked layer.
|
|
*/
|
|
static int _trace_all_marks(struct dev_manager *dm, int flag)
|
|
{
|
|
struct hash_node *hn;
|
|
struct dev_layer *dl;
|
|
|
|
hash_iterate(hn, dm->layers) {
|
|
dl = hash_get_data(dm->layers, hn);
|
|
if (_get_flag(dl, flag) && !_trace_layer_marks(dm, dl, flag)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Marks the top layers, then traces these through the
|
|
* dependencies.
|
|
*/
|
|
static int _mark_lvs(struct dev_manager *dm, struct list *lvs, int flag)
|
|
{
|
|
struct list *lvh;
|
|
struct logical_volume *lv;
|
|
struct dev_layer *dl;
|
|
|
|
list_iterate(lvh, lvs) {
|
|
lv = list_item(lvh, struct lv_list)->lv;
|
|
|
|
if (!(dl = _lookup(dm, lv->lvid.s, NULL))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
_set_flag(dl, flag);
|
|
}
|
|
|
|
if (!_trace_all_marks(dm, flag)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int _suspend_parents(struct dev_manager *dm, struct dev_layer *dl)
|
|
{
|
|
struct list *sh;
|
|
struct dev_layer *dep;
|
|
const char *dlid;
|
|
|
|
list_iterate(sh, &dl->pre_suspend) {
|
|
dlid = list_item(sh, struct str_list)->str;
|
|
|
|
if (!(dep = hash_lookup(dm->layers, dlid))) {
|
|
log_debug("_suspend_parents couldn't find device "
|
|
"layer '%s' - skipping.", dlid);
|
|
continue;
|
|
}
|
|
|
|
if (!strcmp(dep->dlid, dl->dlid)) {
|
|
log_error("BUG: pre-suspend loop detected (%s)", dlid);
|
|
return 0;
|
|
}
|
|
|
|
if (!_suspend_parents(dm, dep)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
if (dep->info.exists & !_suspend(dep)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int _resume_with_deps(struct dev_manager *dm, struct dev_layer *dl)
|
|
{
|
|
struct list *sh;
|
|
struct dev_layer *dep;
|
|
const char *dlid;
|
|
|
|
list_iterate(sh, &dl->pre_create) {
|
|
dlid = list_item(sh, struct str_list)->str;
|
|
|
|
if (!(dep = hash_lookup(dm->layers, dlid))) {
|
|
log_debug("_resume_with_deps couldn't find device "
|
|
"layer '%s' - skipping.", dlid);
|
|
continue;
|
|
}
|
|
|
|
if (!strcmp(dep->dlid, dl->dlid)) {
|
|
log_error("BUG: pre-create loop detected (%s)", dlid);
|
|
return 0;
|
|
}
|
|
|
|
if (!_resume_with_deps(dm, dep)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (dl->info.exists & !_get_flag(dl, SUSPENDED) && !_resume(dl)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Recurses through the tree, ensuring that devices are created
|
|
* in correct order.
|
|
*/
|
|
static int _create_rec(struct dev_manager *dm, struct dev_layer *dl)
|
|
{
|
|
struct list *sh;
|
|
struct dev_layer *dep;
|
|
const char *dlid;
|
|
char *newname, *suffix;
|
|
|
|
/* Suspend? */
|
|
if (_get_flag(dl, SUSPENDED) &&
|
|
(!_suspend_parents(dm, dl) || !_suspend(dl))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
list_iterate(sh, &dl->pre_create) {
|
|
dlid = list_item(sh, struct str_list)->str;
|
|
|
|
if (!(dep = hash_lookup(dm->layers, dlid))) {
|
|
log_error("Couldn't find device layer '%s'.", dlid);
|
|
return 0;
|
|
}
|
|
|
|
if (!strcmp(dep->dlid, dl->dlid)) {
|
|
log_error("BUG: pre-create loop detected (%s)", dlid);
|
|
return 0;
|
|
}
|
|
|
|
if (!_create_rec(dm, dep)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Rename? */
|
|
if (dl->info.exists) {
|
|
if ((suffix = rindex(dl->dlid, '-')))
|
|
suffix++;
|
|
newname = _build_name(dm->mem, dm->vg_name, dl->lv->name,
|
|
suffix);
|
|
if (strcmp(newname, dl->name)) {
|
|
if (!_suspend_parents(dm, dl) ||
|
|
!_suspend(dl) || !_rename(dl, newname)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Create? */
|
|
if (!dl->info.exists) {
|
|
if (!_suspend_parents(dm, dl) ||
|
|
!_load(dm, dl, DM_DEVICE_CREATE)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* Reload? */
|
|
if (_get_flag(dl, RELOAD) &&
|
|
(!_suspend_parents(dm, dl) || !_suspend(dl) ||
|
|
!_load(dm, dl, DM_DEVICE_RELOAD))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int _build_all_layers(struct dev_manager *dm, struct volume_group *vg)
|
|
{
|
|
struct list *lvh;
|
|
struct logical_volume *lvt;
|
|
|
|
/*
|
|
* Build layers for complete vg.
|
|
*/
|
|
list_iterate(lvh, &vg->lvs) {
|
|
lvt = list_item(lvh, struct lv_list)->lv;
|
|
if (!_expand_lv(dm, lvt)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int _fill_in_remove_list(struct dev_manager *dm)
|
|
{
|
|
struct hash_node *hn;
|
|
struct dev_layer *dl;
|
|
struct dl_list *dll;
|
|
|
|
hash_iterate(hn, dm->layers) {
|
|
dl = hash_get_data(dm->layers, hn);
|
|
|
|
if (_get_flag(dl, REMOVE))
|
|
_clear_flag(dl, ACTIVE);
|
|
|
|
if (!_get_flag(dl, ACTIVE)) {
|
|
dll = pool_alloc(dm->mem, sizeof(*dll));
|
|
if (!dll) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
dll->dl = dl;
|
|
list_add(&dm->remove_list, &dll->list);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int _populate_pre_suspend_lists(struct dev_manager *dm)
|
|
{
|
|
struct hash_node *hn;
|
|
struct dev_layer *dl;
|
|
struct list *sh;
|
|
const char *dlid;
|
|
struct dev_layer *dep;
|
|
|
|
hash_iterate(hn, dm->layers) {
|
|
dl = hash_get_data(dm->layers, hn);
|
|
|
|
list_iterate(sh, &dl->pre_suspend) {
|
|
dlid = list_item(sh, struct str_list)->str;
|
|
|
|
if (!(dep = hash_lookup(dm->layers, dlid))) {
|
|
log_debug("_populate_pre_suspend_lists: "
|
|
"Couldn't find device layer '%s' - "
|
|
"skipping.", dlid);
|
|
continue;
|
|
}
|
|
|
|
if (!str_list_add(dm->mem, &dep->pre_create,
|
|
dl->dlid)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
list_iterate(sh, &dl->pre_create) {
|
|
dlid = list_item(sh, struct str_list)->str;
|
|
|
|
if (!(dep = hash_lookup(dm->layers, dlid))) {
|
|
log_debug("_populate_pre_suspend_lists: "
|
|
"Couldn't find device layer '%s' - "
|
|
"skipping.", dlid);
|
|
continue;
|
|
}
|
|
|
|
if (!str_list_add(dm->mem, &dep->pre_suspend,
|
|
dl->dlid)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Layers are removed in a top-down manner.
|
|
*/
|
|
static int _remove_old_layers(struct dev_manager *dm)
|
|
{
|
|
int change;
|
|
struct list *rh, *n;
|
|
struct dev_layer *dl;
|
|
|
|
do {
|
|
change = 0;
|
|
list_iterate_safe(rh, n, &dm->remove_list) {
|
|
dl = list_item(rh, struct dl_list)->dl;
|
|
|
|
if (!dl->info.exists) {
|
|
list_del(rh);
|
|
continue;
|
|
}
|
|
|
|
if (_remove(dl)) {
|
|
change = 1;
|
|
list_del(rh);
|
|
}
|
|
}
|
|
|
|
} while (change);
|
|
|
|
if (!list_empty(&dm->remove_list)) {
|
|
list_iterate(rh, &dm->remove_list) {
|
|
dl = list_item(rh, struct dl_list)->dl;
|
|
log_error("Couldn't deactivate device %s", dl->name);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* The guts of the activation unit, this examines the device
|
|
* layers in the manager, and tries to issue the correct
|
|
* instructions to activate them in order.
|
|
*/
|
|
static int _execute(struct dev_manager *dm, struct volume_group *vg)
|
|
{
|
|
struct hash_node *hn;
|
|
struct dev_layer *dl;
|
|
|
|
if (!_build_all_layers(dm, vg)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Mark all layer that need reloading.
|
|
*/
|
|
_clear_marks(dm, RELOAD);
|
|
if (!_mark_lvs(dm, &dm->reload_list, RELOAD)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Mark all layers that should be active.
|
|
*/
|
|
_clear_marks(dm, ACTIVE);
|
|
if (!_mark_lvs(dm, &dm->active_list, ACTIVE)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Mark all layers that should be left suspended.
|
|
*/
|
|
_clear_marks(dm, SUSPENDED);
|
|
if (!_mark_lvs(dm, &dm->suspend_list, SUSPENDED)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
if (!_fill_in_remove_list(dm)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
if (!_populate_pre_suspend_lists(dm)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Now only top level devices will be unmarked.
|
|
*/
|
|
hash_iterate(hn, dm->layers) {
|
|
dl = hash_get_data(dm->layers, hn);
|
|
|
|
if (_get_flag(dl, ACTIVE) && _get_flag(dl, TOPLEVEL))
|
|
if (!_create_rec(dm, dl)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Resume devices */
|
|
hash_iterate(hn, dm->layers) {
|
|
dl = hash_get_data(dm->layers, hn);
|
|
|
|
if (!_resume_with_deps(dm, dl)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (!_remove_old_layers(dm)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* ATM we decide which vg a layer belongs to by
|
|
* looking at the beginning of the device
|
|
* name.
|
|
*/
|
|
static int _belong_to_vg(const char *vgname, const char *name)
|
|
{
|
|
const char *v = vgname, *n = name;
|
|
|
|
while (*v) {
|
|
if ((*v != *n) || (*v == '-' && *(++n) != '-'))
|
|
return 0;
|
|
v++, n++;
|
|
}
|
|
|
|
if (*n == '-' && *(n + 1) != '-')
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static int _add_existing_layer(struct dev_manager *dm, const char *name)
|
|
{
|
|
struct dev_layer *dl;
|
|
char *copy;
|
|
|
|
log_debug("Found existing layer '%s'", name);
|
|
|
|
if (!(copy = pool_strdup(dm->mem, name))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
if (!(dl = _create_dev(dm, copy, ""))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int _scan_existing_devices(struct dev_manager *dm)
|
|
{
|
|
|
|
int r = 0;
|
|
struct dm_names *names;
|
|
unsigned next = 0;
|
|
|
|
struct dm_task *dmt;
|
|
|
|
if (!(dmt = dm_task_create(DM_DEVICE_LIST)))
|
|
return 0;
|
|
|
|
if (!dm_task_run(dmt))
|
|
goto out;
|
|
|
|
if (!(names = dm_task_get_names(dmt)))
|
|
goto out;
|
|
|
|
r = 1;
|
|
if (!names->dev)
|
|
goto out;
|
|
|
|
do {
|
|
names = (void *) names + next;
|
|
if (_belong_to_vg(dm->vg_name, names->name) &&
|
|
!_add_existing_layer(dm, names->name)) {
|
|
stack;
|
|
r = 0;
|
|
break;
|
|
}
|
|
next = names->next;
|
|
} while (next);
|
|
|
|
out:
|
|
dm_task_destroy(dmt);
|
|
return r;
|
|
}
|
|
|
|
static int _add_lv(struct pool *mem,
|
|
struct list *head, struct logical_volume *lv)
|
|
{
|
|
struct lv_list *lvl;
|
|
|
|
if (!(lvl = pool_alloc(mem, sizeof(*lvl)))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
lvl->lv = lv;
|
|
list_add(head, &lvl->list);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int _add_lvs(struct pool *mem,
|
|
struct list *head, struct logical_volume *origin)
|
|
{
|
|
struct logical_volume *lv;
|
|
struct snapshot *s;
|
|
struct list *lvh;
|
|
|
|
list_iterate(lvh, &origin->vg->lvs) {
|
|
lv = list_item(lvh, struct lv_list)->lv;
|
|
if ((s = find_cow(lv)) && s->origin == origin)
|
|
if (!_add_lv(mem, head, lv))
|
|
return 0;
|
|
}
|
|
|
|
return _add_lv(mem, head, origin);
|
|
}
|
|
|
|
static void _remove_lv(struct list *head, struct logical_volume *lv)
|
|
{
|
|
struct list *lvh;
|
|
struct lv_list *lvl;
|
|
|
|
list_iterate(lvh, head) {
|
|
lvl = list_item(lvh, struct lv_list);
|
|
if (lvl->lv == lv) {
|
|
list_del(lvh);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int _remove_lvs(struct dev_manager *dm, struct logical_volume *lv)
|
|
{
|
|
struct logical_volume *active, *old_origin;
|
|
struct snapshot *s;
|
|
struct list *sh, *active_head;
|
|
|
|
active_head = &dm->active_list;
|
|
|
|
/* Remove any snapshots with given origin */
|
|
list_iterate(sh, active_head) {
|
|
active = list_item(sh, struct lv_list)->lv;
|
|
if ((s = find_cow(active)) && s->origin == lv) {
|
|
_remove_lv(active_head, active);
|
|
}
|
|
}
|
|
|
|
_remove_lv(active_head, lv);
|
|
|
|
if (!(s = find_cow(lv)))
|
|
return 1;
|
|
|
|
old_origin = s->origin;
|
|
|
|
/* Was this the last active snapshot with this origin? */
|
|
list_iterate(sh, active_head) {
|
|
active = list_item(sh, struct lv_list)->lv;
|
|
if ((s = find_cow(active)) && s->origin == old_origin) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return _add_lvs(dm->mem, &dm->reload_list, old_origin);
|
|
}
|
|
|
|
static int _remove_suspended_lvs(struct dev_manager *dm,
|
|
struct logical_volume *lv)
|
|
{
|
|
struct logical_volume *suspended;
|
|
struct snapshot *s;
|
|
struct list *sh, *suspend_head;
|
|
|
|
suspend_head = &dm->suspend_list;
|
|
|
|
/* Remove from list any snapshots with given origin */
|
|
list_iterate(sh, suspend_head) {
|
|
suspended = list_item(sh, struct lv_list)->lv;
|
|
if ((s = find_cow(suspended)) && s->origin == lv) {
|
|
_remove_lv(suspend_head, suspended);
|
|
}
|
|
}
|
|
|
|
_remove_lv(suspend_head, lv);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int _fill_in_active_list(struct dev_manager *dm, struct volume_group *vg)
|
|
{
|
|
char *dlid;
|
|
struct list *lvh;
|
|
struct logical_volume *lv;
|
|
struct dev_layer *dl;
|
|
|
|
list_iterate(lvh, &vg->lvs) {
|
|
lv = list_item(lvh, struct lv_list)->lv;
|
|
|
|
if (!(dlid = _build_dlid(dm->mem, lv->lvid.s, NULL))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
dl = hash_lookup(dm->layers, dlid);
|
|
pool_free(dm->mem, dlid);
|
|
|
|
if (dl) {
|
|
log_debug("Found active lv %s%s", lv->name,
|
|
dl->info.suspended ? " (suspended)" : "");
|
|
|
|
if (!_add_lv(dm->mem, &dm->active_list, lv)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
if (dl->info.suspended) {
|
|
if (!_add_lv(dm->mem, &dm->suspend_list, lv)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int _action(struct dev_manager *dm, struct logical_volume *lv,
|
|
action_t action)
|
|
{
|
|
if (!_scan_existing_devices(dm)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
if (!_fill_in_active_list(dm, lv->vg)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
if (action == ACTIVATE || action == DEACTIVATE)
|
|
/* Get into known state - remove from active list if present */
|
|
if (!_remove_lvs(dm, lv)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
if (action == ACTIVATE) {
|
|
/* Add to active & reload lists */
|
|
if (!_add_lvs(dm->mem, &dm->reload_list, lv) ||
|
|
!_add_lvs(dm->mem, &dm->active_list, lv)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (action == SUSPEND || action == RESUME || action == ACTIVATE)
|
|
/* Get into known state - remove from suspend list if present */
|
|
if (!_remove_suspended_lvs(dm, lv)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
if (action == SUSPEND) {
|
|
if (!_add_lvs(dm->mem, &dm->suspend_list, lv)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (!_execute(dm, lv->vg)) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int dev_manager_activate(struct dev_manager *dm, struct logical_volume *lv)
|
|
{
|
|
return _action(dm, lv, ACTIVATE);
|
|
}
|
|
|
|
int dev_manager_deactivate(struct dev_manager *dm, struct logical_volume *lv)
|
|
{
|
|
return _action(dm, lv, DEACTIVATE);
|
|
}
|
|
|
|
int dev_manager_suspend(struct dev_manager *dm, struct logical_volume *lv)
|
|
{
|
|
return _action(dm, lv, SUSPEND);
|
|
}
|
|
|
|
int dev_manager_mknodes(const struct logical_volume *lv)
|
|
{
|
|
char *name;
|
|
|
|
if (!(name = _build_name(lv->vg->cmd->mem, lv->vg->name,
|
|
lv->name, NULL))) {
|
|
stack;
|
|
return 0;
|
|
}
|
|
|
|
return fs_add_lv(lv, name);
|
|
}
|
|
|
|
int dev_manager_rmnodes(const struct logical_volume *lv)
|
|
{
|
|
return fs_del_lv(lv);
|
|
}
|
|
|
|
void dev_manager_exit(void)
|
|
{
|
|
dm_lib_exit();
|
|
}
|