mirror of
git://sourceware.org/git/lvm2.git
synced 2024-12-22 17:35:59 +03:00
73e679f33f
In the man page and the lvmetad help output.
1708 lines
47 KiB
C
1708 lines
47 KiB
C
/*
|
|
* Copyright (C) 2012-2015 Red Hat, Inc.
|
|
*
|
|
* 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
|
|
*/
|
|
|
|
#define _XOPEN_SOURCE 500 /* pthread */
|
|
|
|
#define _REENTRANT
|
|
|
|
#include "tool.h"
|
|
|
|
#include "daemon-io.h"
|
|
#include "daemon-server.h"
|
|
#include "daemon-log.h"
|
|
#include "lvm-version.h"
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <pthread.h>
|
|
|
|
#define LVMETAD_SOCKET DEFAULT_RUN_DIR "/lvmetad.socket"
|
|
|
|
/*
|
|
* valid/invalid state of cached metadata
|
|
*
|
|
* Normally when using lvmetad, the state is kept up-to-date through a
|
|
* combination of notifications from clients and updates triggered by uevents.
|
|
* When using lvmlockd, the lvmetad state is expected to become out of
|
|
* date (invalid/stale) when other hosts make changes to the metadata on disk.
|
|
*
|
|
* To deal with this, the metadata cached in lvmetad can be flagged as invalid.
|
|
* This invalid flag is returned along with the metadata when read by a
|
|
* command. The command can check for the invalid flag and decide that it
|
|
* should either use the stale metadata (uncommon), or read the latest metadata
|
|
* from disk rather than using the invalid metadata that was returned. If the
|
|
* command reads the latest metadata from disk, it can choose to send it to
|
|
* lvmetad to update the cached copy and clear the invalid flag in lvmetad.
|
|
* Otherwise, the next command to read the metadata from lvmetad will also
|
|
* receive the invalid metadata with the invalid flag (and like the previous
|
|
* command, it too may choose to read the latest metadata from disk and can
|
|
* then also choose to update the lvmetad copy.)
|
|
*
|
|
* For purposes of tracking the invalid state, LVM metadata is considered
|
|
* to be either VG-specific or global. VG-specific metadata is metadata
|
|
* that is isolated to a VG, such as the LVs it contains. Global
|
|
* metadata is metadata that is not isolated to a single VG. Global
|
|
* metdata includes:
|
|
* . the VG namespace (which VG names are used)
|
|
* . the set of orphan PVs (which PVs are in VGs and which are not)
|
|
* . properties of orphan PVs (the size of an orphan PV)
|
|
*
|
|
* If the metadata for a single VG becomes invalid, the VGFL_INVALID
|
|
* flag can be set in the vg_info struct for that VG. If the global
|
|
* metdata becomes invalid, the GLFL_INVALID flag can be set in the
|
|
* lvmetad daemon state.
|
|
*
|
|
* If a command reads VG metadata and VGFL_INVALID is set, an
|
|
* extra config node called "vg_invalid" is added to the config
|
|
* data returned to the command.
|
|
*
|
|
* If a command reads global metdata and GLFL_INVALID is set, an
|
|
* extra config node called "global_invalid" is added to the
|
|
* config data returned to the command.
|
|
*
|
|
* If a command sees vg_invalid, and wants the latest VG metadata,
|
|
* it only needs to scan disks of the PVs in that VG.
|
|
* It can then use vg_update to send the latest metadata to lvmetad
|
|
* which clears the VGFL_INVALID flag.
|
|
*
|
|
* If a command sees global_invalid, and wants the latest metadata,
|
|
* it should scan all devices to update lvmetad, and then send
|
|
* lvmetad the "set_global_info global_invalid=0" message to clear
|
|
* GLFL_INVALID.
|
|
*
|
|
* (When rescanning devices to update lvmetad, the command must use
|
|
* the global filter cmd->lvmetad_filter so that it processes the same
|
|
* devices that are seen by lvmetad.)
|
|
*
|
|
* The lvmetad INVALID flags can be set by sending lvmetad the messages:
|
|
*
|
|
* . set_vg_info with the latest VG seqno. If the VG seqno is larger
|
|
* than the cached VG seqno, VGFL_INVALID is set for the VG.
|
|
*
|
|
* . set_global_info with global_invalid=1 sets GLFL_INVALID.
|
|
*
|
|
* Different entities could use these functions to invalidate metadata
|
|
* if/when they detected that the cache is stale. How they detect that
|
|
* the cache is stale depends on the details of the specific entity.
|
|
*
|
|
* In the case of lvmlockd, it embeds values into its locks to keep track
|
|
* of when other nodes have changed metadata on disk related to those locks.
|
|
* When acquring locks it can look at these values and detect that
|
|
* the metadata associated with the lock has been changed.
|
|
* When the values change, it uses set_vg_info/set_global_info to
|
|
* invalidate the lvmetad cache.
|
|
*
|
|
* The values that lvmlockd distributes through its locks are the
|
|
* latest VG seqno in VG locks and a global counter in the global lock.
|
|
* When a host acquires a VG lock and sees that the embedded seqno is
|
|
* larger than it was previously, it knows that it should invalidate the
|
|
* lvmetad cache for the VG. If the host acquires the global lock
|
|
* and sees that the counter is larger than previously, it knows that
|
|
* it should invalidate the global info in lvmetad. This invalidation
|
|
* is done before the lock is returned to the command. This way the
|
|
* invalid flag will be set on the metadata before the command reads
|
|
* it from lvmetad.
|
|
*/
|
|
|
|
struct vg_info {
|
|
int64_t external_version;
|
|
uint32_t flags; /* VGFL_ */
|
|
};
|
|
|
|
#define GLFL_INVALID 0x00000001
|
|
#define VGFL_INVALID 0x00000001
|
|
|
|
typedef struct {
|
|
daemon_idle *idle;
|
|
log_state *log; /* convenience */
|
|
const char *log_config;
|
|
|
|
struct dm_hash_table *pvid_to_pvmeta;
|
|
struct dm_hash_table *device_to_pvid; /* shares locks with above */
|
|
|
|
struct dm_hash_table *vgid_to_metadata;
|
|
struct dm_hash_table *vgid_to_vgname;
|
|
struct dm_hash_table *vgid_to_outdated_pvs;
|
|
struct dm_hash_table *vgid_to_info;
|
|
struct dm_hash_table *vgname_to_vgid;
|
|
struct dm_hash_table *pvid_to_vgid;
|
|
struct {
|
|
struct dm_hash_table *vg;
|
|
pthread_mutex_t vg_lock_map;
|
|
pthread_mutex_t pvid_to_pvmeta;
|
|
pthread_mutex_t vgid_to_metadata;
|
|
pthread_mutex_t pvid_to_vgid;
|
|
} lock;
|
|
char token[128];
|
|
uint32_t flags; /* GLFL_ */
|
|
pthread_mutex_t token_lock;
|
|
} lvmetad_state;
|
|
|
|
static void destroy_metadata_hashes(lvmetad_state *s)
|
|
{
|
|
struct dm_hash_node *n = NULL;
|
|
|
|
dm_hash_iterate(n, s->vgid_to_metadata)
|
|
dm_config_destroy(dm_hash_get_data(s->vgid_to_metadata, n));
|
|
|
|
dm_hash_iterate(n, s->vgid_to_outdated_pvs)
|
|
dm_config_destroy(dm_hash_get_data(s->vgid_to_outdated_pvs, n));
|
|
|
|
dm_hash_iterate(n, s->pvid_to_pvmeta)
|
|
dm_config_destroy(dm_hash_get_data(s->pvid_to_pvmeta, n));
|
|
|
|
dm_hash_destroy(s->pvid_to_pvmeta);
|
|
dm_hash_destroy(s->vgid_to_metadata);
|
|
dm_hash_destroy(s->vgid_to_vgname);
|
|
dm_hash_destroy(s->vgid_to_outdated_pvs);
|
|
dm_hash_destroy(s->vgid_to_info);
|
|
dm_hash_destroy(s->vgname_to_vgid);
|
|
|
|
dm_hash_destroy(s->device_to_pvid);
|
|
dm_hash_destroy(s->pvid_to_vgid);
|
|
}
|
|
|
|
static void create_metadata_hashes(lvmetad_state *s)
|
|
{
|
|
s->pvid_to_pvmeta = dm_hash_create(32);
|
|
s->device_to_pvid = dm_hash_create(32);
|
|
s->vgid_to_metadata = dm_hash_create(32);
|
|
s->vgid_to_vgname = dm_hash_create(32);
|
|
s->vgid_to_outdated_pvs = dm_hash_create(32);
|
|
s->vgid_to_info = dm_hash_create(32);
|
|
s->pvid_to_vgid = dm_hash_create(32);
|
|
s->vgname_to_vgid = dm_hash_create(32);
|
|
}
|
|
|
|
static void lock_pvid_to_pvmeta(lvmetad_state *s) {
|
|
pthread_mutex_lock(&s->lock.pvid_to_pvmeta); }
|
|
static void unlock_pvid_to_pvmeta(lvmetad_state *s) {
|
|
pthread_mutex_unlock(&s->lock.pvid_to_pvmeta); }
|
|
|
|
static void lock_vgid_to_metadata(lvmetad_state *s) {
|
|
pthread_mutex_lock(&s->lock.vgid_to_metadata); }
|
|
static void unlock_vgid_to_metadata(lvmetad_state *s) {
|
|
pthread_mutex_unlock(&s->lock.vgid_to_metadata); }
|
|
|
|
static void lock_pvid_to_vgid(lvmetad_state *s) {
|
|
pthread_mutex_lock(&s->lock.pvid_to_vgid); }
|
|
static void unlock_pvid_to_vgid(lvmetad_state *s) {
|
|
pthread_mutex_unlock(&s->lock.pvid_to_vgid); }
|
|
|
|
static response reply_fail(const char *reason)
|
|
{
|
|
return daemon_reply_simple("failed", "reason = %s", reason, NULL);
|
|
}
|
|
|
|
static response reply_unknown(const char *reason)
|
|
{
|
|
return daemon_reply_simple("unknown", "reason = %s", reason, NULL);
|
|
}
|
|
|
|
/*
|
|
* TODO: It may be beneficial to clean up the vg lock hash from time to time,
|
|
* since if we have many "rogue" requests for nonexistent things, we will keep
|
|
* allocating memory that we never release. Not good.
|
|
*/
|
|
static struct dm_config_tree *lock_vg(lvmetad_state *s, const char *id) {
|
|
pthread_mutex_t *vg;
|
|
struct dm_config_tree *cft;
|
|
pthread_mutexattr_t rec;
|
|
|
|
pthread_mutex_lock(&s->lock.vg_lock_map);
|
|
if (!(vg = dm_hash_lookup(s->lock.vg, id))) {
|
|
if (!(vg = malloc(sizeof(pthread_mutex_t))) ||
|
|
pthread_mutexattr_init(&rec) ||
|
|
pthread_mutexattr_settype(&rec, PTHREAD_MUTEX_RECURSIVE_NP) ||
|
|
pthread_mutex_init(vg, &rec))
|
|
goto bad;
|
|
if (!dm_hash_insert(s->lock.vg, id, vg)) {
|
|
pthread_mutex_destroy(vg);
|
|
goto bad;
|
|
}
|
|
}
|
|
/* We never remove items from s->lock.vg => the pointer remains valid. */
|
|
pthread_mutex_unlock(&s->lock.vg_lock_map);
|
|
|
|
DEBUGLOG(s, "locking VG %s", id);
|
|
pthread_mutex_lock(vg);
|
|
|
|
/* Protect against structure changes of the vgid_to_metadata hash. */
|
|
lock_vgid_to_metadata(s);
|
|
cft = dm_hash_lookup(s->vgid_to_metadata, id);
|
|
unlock_vgid_to_metadata(s);
|
|
return cft;
|
|
bad:
|
|
pthread_mutex_unlock(&s->lock.vg_lock_map);
|
|
free(vg);
|
|
ERROR(s, "Out of memory");
|
|
return NULL;
|
|
}
|
|
|
|
static void unlock_vg(lvmetad_state *s, const char *id) {
|
|
pthread_mutex_t *vg;
|
|
|
|
DEBUGLOG(s, "unlocking VG %s", id);
|
|
/* Protect the s->lock.vg structure from concurrent access. */
|
|
pthread_mutex_lock(&s->lock.vg_lock_map);
|
|
if ((vg = dm_hash_lookup(s->lock.vg, id)))
|
|
pthread_mutex_unlock(vg);
|
|
pthread_mutex_unlock(&s->lock.vg_lock_map);
|
|
}
|
|
|
|
static struct dm_config_node *pvs(struct dm_config_node *vg)
|
|
{
|
|
struct dm_config_node *pv = dm_config_find_node(vg, "metadata/physical_volumes");
|
|
if (pv)
|
|
pv = pv->child;
|
|
return pv;
|
|
}
|
|
|
|
static void filter_metadata(struct dm_config_node *vg) {
|
|
struct dm_config_node *pv = pvs(vg);
|
|
while (pv) {
|
|
struct dm_config_node *item = pv->child;
|
|
while (item) {
|
|
/* Remove the advisory device nodes. */
|
|
if (item->sib && !strcmp(item->sib->key, "device"))
|
|
item->sib = item->sib->sib;
|
|
item = item->sib;
|
|
}
|
|
pv = pv->sib;
|
|
}
|
|
vg->sib = NULL; /* Drop any trailing garbage. */
|
|
}
|
|
|
|
static void merge_pvmeta(struct dm_config_node *pv, struct dm_config_node *pvmeta)
|
|
{
|
|
struct dm_config_node *tmp;
|
|
|
|
if (!pvmeta)
|
|
return;
|
|
|
|
tmp = pvmeta;
|
|
while (tmp->sib) {
|
|
/* drop the redundant ID and dev_size nodes */
|
|
if (!strcmp(tmp->sib->key, "id") || !strcmp(tmp->sib->key, "dev_size"))
|
|
tmp->sib = tmp->sib->sib;
|
|
if (!tmp->sib) break;
|
|
tmp = tmp->sib;
|
|
tmp->parent = pv;
|
|
}
|
|
tmp->sib = pv->child;
|
|
pv->child = pvmeta;
|
|
pvmeta->parent = pv;
|
|
}
|
|
|
|
/* Either the "big" vgs lock, or a per-vg lock needs to be held before entering
|
|
* this function. */
|
|
static int update_pv_status(lvmetad_state *s,
|
|
struct dm_config_tree *cft,
|
|
struct dm_config_node *vg, int act)
|
|
{
|
|
struct dm_config_node *pv;
|
|
int complete = 1;
|
|
const char *uuid;
|
|
struct dm_config_tree *pvmeta;
|
|
|
|
lock_pvid_to_pvmeta(s);
|
|
|
|
for (pv = pvs(vg); pv; pv = pv->sib) {
|
|
if (!(uuid = dm_config_find_str(pv->child, "id", NULL)))
|
|
continue;
|
|
|
|
pvmeta = dm_hash_lookup(s->pvid_to_pvmeta, uuid);
|
|
if (act) {
|
|
set_flag(cft, pv, "status", "MISSING", !pvmeta);
|
|
if (pvmeta) {
|
|
struct dm_config_node *pvmeta_cn =
|
|
dm_config_clone_node(cft, pvmeta->root->child, 1);
|
|
merge_pvmeta(pv, pvmeta_cn);
|
|
}
|
|
}
|
|
if (!pvmeta) {
|
|
complete = 0;
|
|
if (!act) { /* optimisation */
|
|
unlock_pvid_to_pvmeta(s);
|
|
return complete;
|
|
}
|
|
}
|
|
}
|
|
unlock_pvid_to_pvmeta(s);
|
|
|
|
return complete;
|
|
}
|
|
|
|
static struct dm_config_node *add_last_node(struct dm_config_tree *cft, const char *node_name)
|
|
{
|
|
struct dm_config_node *cn, *last;
|
|
|
|
cn = cft->root;
|
|
last = cn;
|
|
|
|
while (cn->sib) {
|
|
last = cn->sib;
|
|
cn = last;
|
|
}
|
|
|
|
cn = dm_config_create_node(cft, node_name);
|
|
if (!cn)
|
|
return NULL;
|
|
|
|
cn->v = NULL;
|
|
cn->sib = NULL;
|
|
cn->parent = cft->root;
|
|
last->sib = cn;
|
|
|
|
return cn;
|
|
}
|
|
|
|
static struct dm_config_node *make_pv_node(lvmetad_state *s, const char *pvid,
|
|
struct dm_config_tree *cft,
|
|
struct dm_config_node *parent,
|
|
struct dm_config_node *pre_sib)
|
|
{
|
|
struct dm_config_tree *pvmeta = dm_hash_lookup(s->pvid_to_pvmeta, pvid);
|
|
const char *vgid = dm_hash_lookup(s->pvid_to_vgid, pvid), *vgname = NULL;
|
|
struct dm_config_node *pv;
|
|
struct dm_config_node *cn = NULL;
|
|
|
|
if (!pvmeta)
|
|
return NULL;
|
|
|
|
if (vgid) {
|
|
lock_vgid_to_metadata(s); // XXX
|
|
vgname = dm_hash_lookup(s->vgid_to_vgname, vgid);
|
|
unlock_vgid_to_metadata(s);
|
|
}
|
|
|
|
/* Nick the pvmeta config tree. */
|
|
if (!(pv = dm_config_clone_node(cft, pvmeta->root, 0)))
|
|
return 0;
|
|
|
|
if (pre_sib)
|
|
pre_sib->sib = pv;
|
|
if (parent && !parent->child)
|
|
parent->child = pv;
|
|
pv->parent = parent;
|
|
pv->key = pvid;
|
|
|
|
/* Add the "variable" bits to it. */
|
|
|
|
if (vgid && strcmp(vgid, "#orphan"))
|
|
cn = make_text_node(cft, "vgid", vgid, pv, cn);
|
|
if (vgname)
|
|
cn = make_text_node(cft, "vgname", vgname, pv, cn);
|
|
|
|
return pv;
|
|
}
|
|
|
|
static response pv_list(lvmetad_state *s, request r)
|
|
{
|
|
struct dm_config_node *cn = NULL, *cn_pvs;
|
|
struct dm_hash_node *n;
|
|
const char *id;
|
|
response res = { 0 };
|
|
|
|
buffer_init( &res.buffer );
|
|
|
|
if (!(res.cft = dm_config_create()))
|
|
return res; /* FIXME error reporting */
|
|
|
|
/* The response field */
|
|
res.cft->root = make_text_node(res.cft, "response", "OK", NULL, NULL);
|
|
cn_pvs = make_config_node(res.cft, "physical_volumes", NULL, res.cft->root);
|
|
|
|
lock_pvid_to_pvmeta(s);
|
|
|
|
dm_hash_iterate(n, s->pvid_to_pvmeta) {
|
|
id = dm_hash_get_key(s->pvid_to_pvmeta, n);
|
|
cn = make_pv_node(s, id, res.cft, cn_pvs, cn);
|
|
}
|
|
|
|
if (s->flags & GLFL_INVALID)
|
|
add_last_node(res.cft, "global_invalid");
|
|
|
|
unlock_pvid_to_pvmeta(s);
|
|
|
|
return res;
|
|
}
|
|
|
|
static response pv_lookup(lvmetad_state *s, request r)
|
|
{
|
|
const char *pvid = daemon_request_str(r, "uuid", NULL);
|
|
int64_t devt = daemon_request_int(r, "device", 0);
|
|
response res = { 0 };
|
|
struct dm_config_node *pv;
|
|
|
|
buffer_init( &res.buffer );
|
|
|
|
if (!pvid && !devt)
|
|
return reply_fail("need PVID or device");
|
|
|
|
if (!(res.cft = dm_config_create()))
|
|
return reply_fail("out of memory");
|
|
|
|
if (!(res.cft->root = make_text_node(res.cft, "response", "OK", NULL, NULL)))
|
|
return reply_fail("out of memory");
|
|
|
|
lock_pvid_to_pvmeta(s);
|
|
if (!pvid && devt)
|
|
pvid = dm_hash_lookup_binary(s->device_to_pvid, &devt, sizeof(devt));
|
|
|
|
if (!pvid) {
|
|
unlock_pvid_to_pvmeta(s);
|
|
WARN(s, "pv_lookup: could not find device %" PRIu64, devt);
|
|
dm_config_destroy(res.cft);
|
|
return reply_unknown("device not found");
|
|
}
|
|
|
|
pv = make_pv_node(s, pvid, res.cft, NULL, res.cft->root);
|
|
if (!pv) {
|
|
unlock_pvid_to_pvmeta(s);
|
|
dm_config_destroy(res.cft);
|
|
return reply_unknown("PV not found");
|
|
}
|
|
|
|
pv->key = "physical_volume";
|
|
unlock_pvid_to_pvmeta(s);
|
|
|
|
if (s->flags & GLFL_INVALID)
|
|
add_last_node(res.cft, "global_invalid");
|
|
|
|
return res;
|
|
}
|
|
|
|
static response vg_list(lvmetad_state *s, request r)
|
|
{
|
|
struct dm_config_node *cn, *cn_vgs, *cn_last = NULL;
|
|
struct dm_hash_node *n;
|
|
const char *id;
|
|
const char *name;
|
|
response res = { 0 };
|
|
|
|
buffer_init( &res.buffer );
|
|
|
|
if (!(res.cft = dm_config_create()))
|
|
goto bad; /* FIXME: better error reporting */
|
|
|
|
/* The response field */
|
|
res.cft->root = cn = dm_config_create_node(res.cft, "response");
|
|
if (!cn)
|
|
goto bad; /* FIXME */
|
|
cn->parent = res.cft->root;
|
|
if (!(cn->v = dm_config_create_value(res.cft)))
|
|
goto bad; /* FIXME */
|
|
|
|
cn->v->type = DM_CFG_STRING;
|
|
cn->v->v.str = "OK";
|
|
|
|
cn_vgs = cn = cn->sib = dm_config_create_node(res.cft, "volume_groups");
|
|
if (!cn_vgs)
|
|
goto bad; /* FIXME */
|
|
|
|
cn->parent = res.cft->root;
|
|
cn->v = NULL;
|
|
cn->child = NULL;
|
|
|
|
lock_vgid_to_metadata(s);
|
|
|
|
dm_hash_iterate(n, s->vgid_to_vgname) {
|
|
id = dm_hash_get_key(s->vgid_to_vgname, n),
|
|
name = dm_hash_get_data(s->vgid_to_vgname, n);
|
|
|
|
if (!(cn = dm_config_create_node(res.cft, id)))
|
|
goto bad; /* FIXME */
|
|
|
|
if (cn_last)
|
|
cn_last->sib = cn;
|
|
|
|
cn->parent = cn_vgs;
|
|
cn->sib = NULL;
|
|
cn->v = NULL;
|
|
|
|
if (!(cn->child = dm_config_create_node(res.cft, "name")))
|
|
goto bad; /* FIXME */
|
|
|
|
cn->child->parent = cn;
|
|
cn->child->sib = 0;
|
|
if (!(cn->child->v = dm_config_create_value(res.cft)))
|
|
goto bad; /* FIXME */
|
|
|
|
cn->child->v->type = DM_CFG_STRING;
|
|
cn->child->v->v.str = name;
|
|
|
|
if (!cn_vgs->child)
|
|
cn_vgs->child = cn;
|
|
cn_last = cn;
|
|
}
|
|
|
|
unlock_vgid_to_metadata(s);
|
|
|
|
if (s->flags & GLFL_INVALID)
|
|
add_last_node(res.cft, "global_invalid");
|
|
bad:
|
|
return res;
|
|
}
|
|
|
|
static void mark_outdated_pv(lvmetad_state *s, const char *vgid, const char *pvid)
|
|
{
|
|
struct dm_config_tree *pvmeta, *outdated_pvs;
|
|
struct dm_config_node *list, *cft_vgid;
|
|
struct dm_config_value *v;
|
|
|
|
lock_pvid_to_pvmeta(s);
|
|
pvmeta = dm_hash_lookup(s->pvid_to_pvmeta, pvid);
|
|
unlock_pvid_to_pvmeta(s);
|
|
|
|
/* if the MDA exists and is used, it will have ignore=0 set */
|
|
if (!pvmeta ||
|
|
(dm_config_find_int64(pvmeta->root, "pvmeta/mda0/ignore", 1) &&
|
|
dm_config_find_int64(pvmeta->root, "pvmeta/mda1/ignore", 1)))
|
|
return;
|
|
|
|
WARN(s, "PV %s has outdated metadata", pvid);
|
|
|
|
outdated_pvs = dm_hash_lookup(s->vgid_to_outdated_pvs, vgid);
|
|
if (!outdated_pvs) {
|
|
if (!(outdated_pvs = dm_config_from_string("outdated_pvs/pv_list = []")) ||
|
|
!(cft_vgid = make_text_node(outdated_pvs, "vgid", dm_pool_strdup(outdated_pvs->mem, vgid),
|
|
outdated_pvs->root, NULL)))
|
|
abort();
|
|
if (!dm_hash_insert(s->vgid_to_outdated_pvs, cft_vgid->v->v.str, outdated_pvs))
|
|
abort();
|
|
DEBUGLOG(s, "created outdated_pvs list for VG %s", vgid);
|
|
}
|
|
|
|
list = dm_config_find_node(outdated_pvs->root, "outdated_pvs/pv_list");
|
|
v = list->v;
|
|
while (v) {
|
|
if (v->type != DM_CFG_EMPTY_ARRAY && !strcmp(v->v.str, pvid))
|
|
return;
|
|
v = v->next;
|
|
}
|
|
if (!(v = dm_config_create_value(outdated_pvs)))
|
|
abort();
|
|
v->type = DM_CFG_STRING;
|
|
v->v.str = dm_pool_strdup(outdated_pvs->mem, pvid);
|
|
v->next = list->v;
|
|
list->v = v;
|
|
}
|
|
|
|
static void chain_outdated_pvs(lvmetad_state *s, const char *vgid, struct dm_config_tree *metadata_cft, struct dm_config_node *metadata)
|
|
{
|
|
struct dm_config_tree *cft = dm_hash_lookup(s->vgid_to_outdated_pvs, vgid), *pvmeta;
|
|
struct dm_config_node *pv, *res, *out_pvs = cft ? dm_config_find_node(cft->root, "outdated_pvs/pv_list") : NULL;
|
|
struct dm_config_value *pvs_v = out_pvs ? out_pvs->v : NULL;
|
|
if (!pvs_v)
|
|
return;
|
|
if (!(res = make_config_node(metadata_cft, "outdated_pvs", metadata_cft->root, 0)))
|
|
return; /* oops */
|
|
res->sib = metadata->child;
|
|
metadata->child = res;
|
|
for (; pvs_v && pvs_v->type != DM_CFG_EMPTY_ARRAY; pvs_v = pvs_v->next) {
|
|
pvmeta = dm_hash_lookup(s->pvid_to_pvmeta, pvs_v->v.str);
|
|
if (!pvmeta) {
|
|
WARN(s, "metadata for PV %s not found", pvs_v->v.str);
|
|
continue;
|
|
}
|
|
if (!(pv = dm_config_clone_node(metadata_cft, pvmeta->root, 0)))
|
|
continue;
|
|
pv->key = dm_config_find_str(pv, "pvmeta/id", NULL);
|
|
pv->sib = res->child;
|
|
res->child = pv;
|
|
}
|
|
}
|
|
|
|
static response vg_lookup(lvmetad_state *s, request r)
|
|
{
|
|
struct dm_config_tree *cft;
|
|
struct dm_config_node *metadata, *n;
|
|
struct vg_info *info;
|
|
response res = { 0 };
|
|
|
|
const char *uuid = daemon_request_str(r, "uuid", NULL);
|
|
const char *name = daemon_request_str(r, "name", NULL);
|
|
|
|
buffer_init( &res.buffer );
|
|
|
|
DEBUGLOG(s, "vg_lookup: uuid = %s, name = %s", uuid, name);
|
|
|
|
if (!uuid || !name) {
|
|
lock_vgid_to_metadata(s);
|
|
if (name && !uuid)
|
|
uuid = dm_hash_lookup(s->vgname_to_vgid, name);
|
|
if (uuid && !name)
|
|
name = dm_hash_lookup(s->vgid_to_vgname, uuid);
|
|
unlock_vgid_to_metadata(s);
|
|
}
|
|
|
|
DEBUGLOG(s, "vg_lookup: updated uuid = %s, name = %s", uuid, name);
|
|
|
|
/* Check the name here. */
|
|
if (!uuid || !name)
|
|
return reply_unknown("VG not found");
|
|
|
|
cft = lock_vg(s, uuid);
|
|
if (!cft || !cft->root) {
|
|
unlock_vg(s, uuid);
|
|
return reply_unknown("UUID not found");
|
|
}
|
|
|
|
metadata = cft->root;
|
|
if (!(res.cft = dm_config_create()))
|
|
goto bad;
|
|
|
|
/* The response field */
|
|
if (!(res.cft->root = n = dm_config_create_node(res.cft, "response")))
|
|
goto bad;
|
|
|
|
if (!(n->v = dm_config_create_value(cft)))
|
|
goto bad;
|
|
|
|
n->parent = res.cft->root;
|
|
n->v->type = DM_CFG_STRING;
|
|
n->v->v.str = "OK";
|
|
|
|
if (!(n = n->sib = dm_config_create_node(res.cft, "name")))
|
|
goto bad;
|
|
|
|
if (!(n->v = dm_config_create_value(res.cft)))
|
|
goto bad;
|
|
|
|
n->parent = res.cft->root;
|
|
n->v->type = DM_CFG_STRING;
|
|
n->v->v.str = name;
|
|
|
|
/* The metadata section */
|
|
if (!(n = n->sib = dm_config_clone_node(res.cft, metadata, 1)))
|
|
goto bad;
|
|
n->parent = res.cft->root;
|
|
unlock_vg(s, uuid);
|
|
|
|
update_pv_status(s, res.cft, n, 1); /* FIXME report errors */
|
|
chain_outdated_pvs(s, uuid, res.cft, n);
|
|
|
|
if (s->flags & GLFL_INVALID)
|
|
add_last_node(res.cft, "global_invalid");
|
|
|
|
info = dm_hash_lookup(s->vgid_to_info, uuid);
|
|
if (info && (info->flags & VGFL_INVALID)) {
|
|
n = add_last_node(res.cft, "vg_invalid");
|
|
if (!n)
|
|
goto bad;
|
|
}
|
|
|
|
return res;
|
|
bad:
|
|
unlock_vg(s, uuid);
|
|
return reply_fail("out of memory");
|
|
}
|
|
|
|
static int vg_remove_if_missing(lvmetad_state *s, const char *vgid, int update_pvids);
|
|
|
|
enum update_pvid_mode { UPDATE_ONLY, REMOVE_EMPTY, MARK_OUTDATED };
|
|
|
|
/* You need to be holding the pvid_to_vgid lock already to call this. */
|
|
static int update_pvid_to_vgid(lvmetad_state *s, struct dm_config_tree *vg,
|
|
const char *vgid, int mode)
|
|
{
|
|
struct dm_config_node *pv;
|
|
struct dm_hash_table *to_check;
|
|
struct dm_hash_node *n;
|
|
const char *pvid;
|
|
const char *vgid_old;
|
|
const char *check_vgid;
|
|
int r = 0;
|
|
|
|
if (!vgid)
|
|
return 0;
|
|
|
|
if (!(to_check = dm_hash_create(32)))
|
|
return 0;
|
|
|
|
for (pv = pvs(vg->root); pv; pv = pv->sib) {
|
|
if (!(pvid = dm_config_find_str(pv->child, "id", NULL)))
|
|
continue;
|
|
|
|
if (mode == REMOVE_EMPTY &&
|
|
(vgid_old = dm_hash_lookup(s->pvid_to_vgid, pvid)) &&
|
|
!dm_hash_insert(to_check, vgid_old, (void*) 1))
|
|
goto out;
|
|
|
|
if (mode == MARK_OUTDATED)
|
|
mark_outdated_pv(s, vgid, pvid);
|
|
|
|
if (!dm_hash_insert(s->pvid_to_vgid, pvid, (void*) vgid))
|
|
goto out;
|
|
|
|
DEBUGLOG(s, "moving PV %s to VG %s", pvid, vgid);
|
|
}
|
|
|
|
dm_hash_iterate(n, to_check) {
|
|
check_vgid = dm_hash_get_key(to_check, n);
|
|
lock_vg(s, check_vgid);
|
|
vg_remove_if_missing(s, check_vgid, 0);
|
|
unlock_vg(s, check_vgid);
|
|
}
|
|
|
|
r = 1;
|
|
out:
|
|
dm_hash_destroy(to_check);
|
|
|
|
return r;
|
|
}
|
|
|
|
/* A pvid map lock needs to be held if update_pvids = 1. */
|
|
static int remove_metadata(lvmetad_state *s, const char *vgid, int update_pvids)
|
|
{
|
|
struct dm_config_tree *old, *outdated_pvs;
|
|
const char *oldname;
|
|
lock_vgid_to_metadata(s);
|
|
old = dm_hash_lookup(s->vgid_to_metadata, vgid);
|
|
outdated_pvs = dm_hash_lookup(s->vgid_to_outdated_pvs, vgid);
|
|
oldname = dm_hash_lookup(s->vgid_to_vgname, vgid);
|
|
|
|
if (!old) {
|
|
unlock_vgid_to_metadata(s);
|
|
return 0;
|
|
}
|
|
|
|
assert(oldname);
|
|
|
|
/* need to update what we have since we found a newer version */
|
|
dm_hash_remove(s->vgid_to_metadata, vgid);
|
|
dm_hash_remove(s->vgid_to_vgname, vgid);
|
|
dm_hash_remove(s->vgname_to_vgid, oldname);
|
|
dm_hash_remove(s->vgid_to_outdated_pvs, vgid);
|
|
unlock_vgid_to_metadata(s);
|
|
|
|
if (update_pvids)
|
|
/* FIXME: What should happen when update fails */
|
|
update_pvid_to_vgid(s, old, "#orphan", 0);
|
|
dm_config_destroy(old);
|
|
if (outdated_pvs)
|
|
dm_config_destroy(outdated_pvs);
|
|
return 1;
|
|
}
|
|
|
|
/* The VG must be locked. */
|
|
static int vg_remove_if_missing(lvmetad_state *s, const char *vgid, int update_pvids)
|
|
{
|
|
struct dm_config_tree *vg;
|
|
struct dm_config_node *pv;
|
|
const char *vgid_check;
|
|
const char *pvid;
|
|
int missing = 1;
|
|
|
|
if (!vgid)
|
|
return 0;
|
|
|
|
if (!(vg = dm_hash_lookup(s->vgid_to_metadata, vgid)))
|
|
return 1;
|
|
|
|
lock_pvid_to_pvmeta(s);
|
|
for (pv = pvs(vg->root); pv; pv = pv->sib) {
|
|
if (!(pvid = dm_config_find_str(pv->child, "id", NULL)))
|
|
continue;
|
|
|
|
if ((vgid_check = dm_hash_lookup(s->pvid_to_vgid, pvid)) &&
|
|
dm_hash_lookup(s->pvid_to_pvmeta, pvid) &&
|
|
!strcmp(vgid, vgid_check))
|
|
missing = 0; /* at least one PV is around */
|
|
}
|
|
|
|
if (missing) {
|
|
DEBUGLOG(s, "removing empty VG %s", vgid);
|
|
remove_metadata(s, vgid, update_pvids);
|
|
}
|
|
|
|
unlock_pvid_to_pvmeta(s);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* No locks need to be held. The pointers are never used outside of the scope of
|
|
* this function, so they can be safely destroyed after update_metadata returns
|
|
* (anything that might have been retained is copied). */
|
|
static int update_metadata(lvmetad_state *s, const char *name, const char *_vgid,
|
|
struct dm_config_node *metadata, int64_t *oldseq, const char *pvid)
|
|
{
|
|
struct dm_config_tree *cft = NULL;
|
|
struct dm_config_tree *old;
|
|
int retval = 0;
|
|
int seq;
|
|
int haveseq = -1;
|
|
const char *oldname = NULL;
|
|
const char *vgid;
|
|
char *cfgname;
|
|
|
|
lock_vgid_to_metadata(s);
|
|
old = dm_hash_lookup(s->vgid_to_metadata, _vgid);
|
|
oldname = dm_hash_lookup(s->vgid_to_vgname, _vgid);
|
|
unlock_vgid_to_metadata(s);
|
|
lock_vg(s, _vgid);
|
|
|
|
seq = dm_config_find_int(metadata, "metadata/seqno", -1);
|
|
|
|
if (old)
|
|
haveseq = dm_config_find_int(old->root, "metadata/seqno", -1);
|
|
|
|
if (seq < 0)
|
|
goto out;
|
|
|
|
filter_metadata(metadata); /* sanitize */
|
|
|
|
if (oldseq) {
|
|
if (old)
|
|
*oldseq = haveseq;
|
|
else
|
|
*oldseq = seq;
|
|
}
|
|
|
|
if (seq == haveseq) {
|
|
retval = 1;
|
|
if (compare_config(metadata, old->root))
|
|
retval = 0;
|
|
DEBUGLOG(s, "Not updating metadata for %s at %d (%s)", _vgid, haveseq,
|
|
retval ? "ok" : "MISMATCH");
|
|
if (!retval) {
|
|
DEBUGLOG_cft(s, "OLD: ", old->root);
|
|
DEBUGLOG_cft(s, "NEW: ", metadata);
|
|
}
|
|
goto out;
|
|
}
|
|
|
|
if (seq < haveseq) {
|
|
DEBUGLOG(s, "Refusing to update metadata for %s (at %d) to %d", _vgid, haveseq, seq);
|
|
|
|
if (pvid)
|
|
mark_outdated_pv(s, dm_config_find_str(old->root, "metadata/id", NULL), pvid);
|
|
|
|
/* TODO: notify the client that their metadata is out of date? */
|
|
retval = 1;
|
|
goto out;
|
|
}
|
|
|
|
if (!(cft = dm_config_create()) ||
|
|
!(cft->root = dm_config_clone_node(cft, metadata, 0))) {
|
|
ERROR(s, "Out of memory");
|
|
goto out;
|
|
}
|
|
|
|
vgid = dm_config_find_str(cft->root, "metadata/id", NULL);
|
|
|
|
if (!vgid || !name) {
|
|
DEBUGLOG(s, "Name '%s' or uuid '%s' missing!", name, vgid);
|
|
goto out;
|
|
}
|
|
|
|
lock_pvid_to_vgid(s);
|
|
|
|
if (haveseq >= 0 && haveseq < seq) {
|
|
INFO(s, "Updating metadata for %s at %d to %d", _vgid, haveseq, seq);
|
|
if (oldseq)
|
|
update_pvid_to_vgid(s, old, vgid, MARK_OUTDATED);
|
|
/* temporarily orphan all of our PVs */
|
|
update_pvid_to_vgid(s, old, "#orphan", 0);
|
|
}
|
|
|
|
lock_vgid_to_metadata(s);
|
|
DEBUGLOG(s, "Mapping %s to %s", vgid, name);
|
|
|
|
retval = ((cfgname = dm_pool_strdup(dm_config_memory(cft), name)) &&
|
|
dm_hash_insert(s->vgid_to_metadata, vgid, cft) &&
|
|
dm_hash_insert(s->vgid_to_vgname, vgid, cfgname) &&
|
|
dm_hash_insert(s->vgname_to_vgid, name, (void*) vgid)) ? 1 : 0;
|
|
|
|
if (retval && oldname && strcmp(name, oldname)) {
|
|
const char *vgid_prev = dm_hash_lookup(s->vgname_to_vgid, oldname);
|
|
if (vgid_prev && !strcmp(vgid_prev, vgid))
|
|
dm_hash_remove(s->vgname_to_vgid, oldname);
|
|
}
|
|
|
|
if (haveseq >= 0 && haveseq < seq)
|
|
dm_config_destroy(old);
|
|
|
|
unlock_vgid_to_metadata(s);
|
|
|
|
if (retval)
|
|
retval = update_pvid_to_vgid(s, cft, vgid, 1);
|
|
|
|
unlock_pvid_to_vgid(s);
|
|
out: /* FIXME: We should probably abort() on partial failures. */
|
|
if (!retval && cft)
|
|
dm_config_destroy(cft);
|
|
unlock_vg(s, _vgid);
|
|
return retval;
|
|
}
|
|
|
|
static dev_t device_remove(lvmetad_state *s, struct dm_config_tree *pvmeta, dev_t device)
|
|
{
|
|
struct dm_config_node *pvmeta_tmp;
|
|
struct dm_config_value *v = NULL;
|
|
dev_t alt_device = 0, prim_device = 0;
|
|
|
|
if ((pvmeta_tmp = dm_config_find_node(pvmeta->root, "pvmeta/devices_alternate")))
|
|
v = pvmeta_tmp->v;
|
|
|
|
prim_device = dm_config_find_int64(pvmeta->root, "pvmeta/device", 0);
|
|
|
|
/* it is the primary device */
|
|
if (device > 0 && device == prim_device && pvmeta_tmp && pvmeta_tmp->v)
|
|
{
|
|
alt_device = pvmeta_tmp->v->v.i;
|
|
pvmeta_tmp->v = pvmeta_tmp->v->next;
|
|
pvmeta_tmp = dm_config_find_node(pvmeta->root, "pvmeta/device");
|
|
pvmeta_tmp->v->v.i = alt_device;
|
|
} else if (device != prim_device)
|
|
alt_device = prim_device;
|
|
|
|
/* it is an alternate device */
|
|
if (device > 0 && v && v->v.i == device)
|
|
pvmeta_tmp->v = v->next;
|
|
else while (device > 0 && pvmeta_tmp && v) {
|
|
if (v->next && v->next->v.i == device)
|
|
v->next = v->next->next;
|
|
v = v->next;
|
|
}
|
|
|
|
return alt_device;
|
|
}
|
|
|
|
static response pv_gone(lvmetad_state *s, request r)
|
|
{
|
|
const char *pvid = daemon_request_str(r, "uuid", NULL);
|
|
int64_t device = daemon_request_int(r, "device", 0);
|
|
int64_t alt_device = 0;
|
|
struct dm_config_tree *pvmeta;
|
|
char *vgid;
|
|
|
|
DEBUGLOG(s, "pv_gone: %s / %" PRIu64, pvid, device);
|
|
|
|
lock_pvid_to_pvmeta(s);
|
|
if (!pvid && device > 0)
|
|
pvid = dm_hash_lookup_binary(s->device_to_pvid, &device, sizeof(device));
|
|
if (!pvid) {
|
|
unlock_pvid_to_pvmeta(s);
|
|
return reply_unknown("device not in cache");
|
|
}
|
|
|
|
DEBUGLOG(s, "pv_gone (updated): %s / %" PRIu64, pvid, device);
|
|
|
|
if (!(pvmeta = dm_hash_lookup(s->pvid_to_pvmeta, pvid)))
|
|
return reply_unknown("PVID does not exist");
|
|
vgid = dm_hash_lookup(s->pvid_to_vgid, pvid);
|
|
|
|
dm_hash_remove_binary(s->device_to_pvid, &device, sizeof(device));
|
|
|
|
if (!(alt_device = device_remove(s, pvmeta, device)))
|
|
dm_hash_remove(s->pvid_to_pvmeta, pvid);
|
|
|
|
DEBUGLOG(s, "pv_gone alt_device = %" PRIu64, alt_device);
|
|
|
|
unlock_pvid_to_pvmeta(s);
|
|
|
|
if (vgid) {
|
|
if (!(vgid = dm_strdup(vgid)))
|
|
return reply_fail("out of memory");
|
|
|
|
lock_vg(s, vgid);
|
|
vg_remove_if_missing(s, vgid, 1);
|
|
unlock_vg(s, vgid);
|
|
dm_free(vgid);
|
|
}
|
|
|
|
if (!alt_device)
|
|
dm_config_destroy(pvmeta);
|
|
|
|
if (alt_device) {
|
|
return daemon_reply_simple("OK",
|
|
"device = %"PRId64, alt_device,
|
|
NULL);
|
|
} else
|
|
return daemon_reply_simple("OK", NULL );
|
|
}
|
|
|
|
static response pv_clear_all(lvmetad_state *s, request r)
|
|
{
|
|
DEBUGLOG(s, "pv_clear_all");
|
|
|
|
lock_pvid_to_pvmeta(s);
|
|
lock_pvid_to_vgid(s);
|
|
lock_vgid_to_metadata(s);
|
|
|
|
destroy_metadata_hashes(s);
|
|
create_metadata_hashes(s);
|
|
|
|
unlock_pvid_to_vgid(s);
|
|
unlock_vgid_to_metadata(s);
|
|
unlock_pvid_to_pvmeta(s);
|
|
|
|
return daemon_reply_simple("OK", NULL);
|
|
}
|
|
|
|
static response pv_found(lvmetad_state *s, request r)
|
|
{
|
|
struct dm_config_node *metadata = dm_config_find_node(r.cft->root, "metadata");
|
|
const char *pvid = daemon_request_str(r, "pvmeta/id", NULL);
|
|
const char *vgname = daemon_request_str(r, "vgname", NULL);
|
|
const char *vgid = daemon_request_str(r, "metadata/id", NULL);
|
|
const char *vgid_old = NULL;
|
|
struct dm_config_node *pvmeta = dm_config_find_node(r.cft->root, "pvmeta"), *altdev = NULL;
|
|
struct dm_config_value *altdev_v;
|
|
uint64_t device, device_old_pvid = 0;
|
|
struct dm_config_tree *cft, *pvmeta_old_dev = NULL, *pvmeta_old_pvid = NULL;
|
|
char *old;
|
|
int complete = 0, orphan = 0;
|
|
int64_t seqno = -1, seqno_old = -1, changed = 0;
|
|
|
|
if (!pvid)
|
|
return reply_fail("need PV UUID");
|
|
if (!pvmeta)
|
|
return reply_fail("need PV metadata");
|
|
|
|
if (!dm_config_get_uint64(pvmeta, "pvmeta/device", &device))
|
|
return reply_fail("need PV device number");
|
|
|
|
if (!(cft = dm_config_create()))
|
|
return reply_fail("out of memory");
|
|
|
|
lock_pvid_to_pvmeta(s);
|
|
|
|
if ((pvmeta_old_pvid = dm_hash_lookup(s->pvid_to_pvmeta, pvid)))
|
|
dm_config_get_uint64(pvmeta_old_pvid->root, "pvmeta/device", &device_old_pvid);
|
|
|
|
if ((old = dm_hash_lookup_binary(s->device_to_pvid, &device, sizeof(device)))) {
|
|
pvmeta_old_dev = dm_hash_lookup(s->pvid_to_pvmeta, old);
|
|
vgid_old = dm_hash_lookup(s->pvid_to_vgid, old);
|
|
}
|
|
|
|
DEBUGLOG(s, "pv_found %s, vgid = %s, device = %" PRIu64 " (previously %" PRIu64 "), old = %s",
|
|
pvid, vgid, device, device_old_pvid, old);
|
|
|
|
if (!(cft->root = dm_config_clone_node(cft, pvmeta, 0)))
|
|
goto out_of_mem;
|
|
|
|
pvid = dm_config_find_str(cft->root, "pvmeta/id", NULL);
|
|
|
|
if (!pvmeta_old_pvid || compare_config(pvmeta_old_pvid->root, cft->root))
|
|
changed |= 1;
|
|
|
|
if (pvmeta_old_pvid && device != device_old_pvid) {
|
|
DEBUGLOG(s, "PV %s duplicated on device %" PRIu64, pvid, device_old_pvid);
|
|
dm_hash_remove_binary(s->device_to_pvid, &device_old_pvid, sizeof(device_old_pvid));
|
|
if (!dm_hash_insert_binary(s->device_to_pvid, &device_old_pvid,
|
|
sizeof(device_old_pvid), (void*)pvid))
|
|
goto out_of_mem;
|
|
if ((altdev = dm_config_find_node(pvmeta_old_pvid->root, "pvmeta/devices_alternate"))) {
|
|
altdev = dm_config_clone_node(cft, altdev, 0);
|
|
chain_node(altdev, cft->root, 0);
|
|
} else
|
|
if (!(altdev = make_config_node(cft, "devices_alternate", cft->root, 0)))
|
|
goto out_of_mem;
|
|
altdev_v = altdev->v;
|
|
while (1) {
|
|
if (altdev_v && altdev_v->v.i == device_old_pvid)
|
|
break;
|
|
if (altdev_v)
|
|
altdev_v = altdev_v->next;
|
|
if (!altdev_v) {
|
|
if (!(altdev_v = dm_config_create_value(cft)))
|
|
goto out_of_mem;
|
|
altdev_v->next = altdev->v;
|
|
altdev->v = altdev_v;
|
|
altdev->v->v.i = device_old_pvid;
|
|
break;
|
|
}
|
|
};
|
|
altdev_v = altdev->v;
|
|
while (altdev_v) {
|
|
if (altdev_v->next && altdev_v->next->v.i == device)
|
|
altdev_v->next = altdev_v->next->next;
|
|
altdev_v = altdev_v->next;
|
|
}
|
|
changed |= 1;
|
|
}
|
|
|
|
if (!dm_hash_insert(s->pvid_to_pvmeta, pvid, cft) ||
|
|
!dm_hash_insert_binary(s->device_to_pvid, &device, sizeof(device), (void*)pvid)) {
|
|
dm_hash_remove(s->pvid_to_pvmeta, pvid);
|
|
out_of_mem:
|
|
unlock_pvid_to_pvmeta(s);
|
|
dm_config_destroy(cft);
|
|
dm_free(old);
|
|
return reply_fail("out of memory");
|
|
}
|
|
|
|
unlock_pvid_to_pvmeta(s);
|
|
|
|
if (pvmeta_old_pvid)
|
|
dm_config_destroy(pvmeta_old_pvid);
|
|
if (pvmeta_old_dev && pvmeta_old_dev != pvmeta_old_pvid) {
|
|
dev_t d = dm_config_find_int64(pvmeta_old_dev->root, "pvmeta/device", 0);
|
|
WARN(s, "pv_found: stray device %"PRId64, d);
|
|
if (!device_remove(s, pvmeta_old_dev, device)) {
|
|
dm_hash_remove(s->pvid_to_pvmeta, old);
|
|
dm_config_destroy(pvmeta_old_dev);
|
|
}
|
|
}
|
|
|
|
if (metadata) {
|
|
if (!vgid)
|
|
return reply_fail("need VG UUID");
|
|
DEBUGLOG(s, "obtained vgid = %s, vgname = %s", vgid, vgname);
|
|
if (!vgname)
|
|
return reply_fail("need VG name");
|
|
if (daemon_request_int(r, "metadata/seqno", -1) < 0)
|
|
return reply_fail("need VG seqno");
|
|
|
|
if (!update_metadata(s, vgname, vgid, metadata, &seqno_old, pvid))
|
|
return reply_fail("metadata update failed");
|
|
changed |= (seqno_old != dm_config_find_int(metadata, "metadata/seqno", -1));
|
|
} else {
|
|
lock_pvid_to_vgid(s);
|
|
vgid = dm_hash_lookup(s->pvid_to_vgid, pvid);
|
|
unlock_pvid_to_vgid(s);
|
|
}
|
|
|
|
if (vgid) {
|
|
if ((cft = lock_vg(s, vgid))) {
|
|
complete = update_pv_status(s, cft, cft->root, 0);
|
|
seqno = dm_config_find_int(cft->root, "metadata/seqno", -1);
|
|
} else if (!strcmp(vgid, "#orphan"))
|
|
orphan = 1;
|
|
else {
|
|
unlock_vg(s, vgid);
|
|
return reply_fail("non-orphan VG without metadata encountered");
|
|
}
|
|
unlock_vg(s, vgid);
|
|
|
|
// TODO: separate vgid->vgname lock
|
|
lock_vgid_to_metadata(s);
|
|
vgname = dm_hash_lookup(s->vgid_to_vgname, vgid);
|
|
unlock_vgid_to_metadata(s);
|
|
}
|
|
|
|
if (vgid_old && (!vgid || strcmp(vgid, vgid_old))) {
|
|
/* make a copy, because vg_remove_if_missing will deallocate the
|
|
* storage behind vgid_old */
|
|
vgid_old = dm_strdup(vgid_old);
|
|
lock_vg(s, vgid_old);
|
|
vg_remove_if_missing(s, vgid_old, 1);
|
|
unlock_vg(s, vgid_old);
|
|
dm_free((char*)vgid_old);
|
|
}
|
|
|
|
return daemon_reply_simple("OK",
|
|
"status = %s", orphan ? "orphan" :
|
|
(complete ? "complete" : "partial"),
|
|
"changed = %d", changed,
|
|
"vgid = %s", vgid ? vgid : "#orphan",
|
|
"vgname = %s", vgname ? vgname : "#orphan",
|
|
"seqno_before = %"PRId64, seqno_old,
|
|
"seqno_after = %"PRId64, seqno,
|
|
NULL);
|
|
}
|
|
|
|
static response vg_clear_outdated_pvs(lvmetad_state *s, request r)
|
|
{
|
|
struct dm_config_tree *outdated_pvs;
|
|
const char *vgid = daemon_request_str(r, "vgid", NULL);
|
|
|
|
if (!vgid)
|
|
return reply_fail("need VG UUID");
|
|
|
|
if ((outdated_pvs = dm_hash_lookup(s->vgid_to_outdated_pvs, vgid))) {
|
|
dm_config_destroy(outdated_pvs);
|
|
dm_hash_remove(s->vgid_to_outdated_pvs, vgid);
|
|
}
|
|
return daemon_reply_simple("OK", NULL);
|
|
}
|
|
|
|
static void vg_info_update(lvmetad_state *s, const char *uuid,
|
|
struct dm_config_node *metadata)
|
|
{
|
|
struct vg_info *info;
|
|
int64_t cache_version;
|
|
|
|
cache_version = dm_config_find_int64(metadata, "metadata/seqno", -1);
|
|
if (cache_version == -1)
|
|
return;
|
|
|
|
info = (struct vg_info *) dm_hash_lookup(s->vgid_to_info, uuid);
|
|
if (!info)
|
|
return;
|
|
|
|
if (cache_version >= info->external_version)
|
|
info->flags &= ~VGFL_INVALID;
|
|
}
|
|
|
|
static response vg_update(lvmetad_state *s, request r)
|
|
{
|
|
struct dm_config_node *metadata = dm_config_find_node(r.cft->root, "metadata");
|
|
const char *vgid = daemon_request_str(r, "metadata/id", NULL);
|
|
const char *vgname = daemon_request_str(r, "vgname", NULL);
|
|
if (metadata) {
|
|
if (!vgid)
|
|
return reply_fail("need VG UUID");
|
|
if (!vgname)
|
|
return reply_fail("need VG name");
|
|
if (daemon_request_int(r, "metadata/seqno", -1) < 0)
|
|
return reply_fail("need VG seqno");
|
|
|
|
/* TODO defer metadata update here; add a separate vg_commit
|
|
* call; if client does not commit, die */
|
|
if (!update_metadata(s, vgname, vgid, metadata, NULL, NULL))
|
|
return reply_fail("metadata update failed");
|
|
|
|
vg_info_update(s, vgid, metadata);
|
|
}
|
|
return daemon_reply_simple("OK", NULL);
|
|
}
|
|
|
|
static response vg_remove(lvmetad_state *s, request r)
|
|
{
|
|
const char *vgid = daemon_request_str(r, "uuid", NULL);
|
|
|
|
if (!vgid)
|
|
return reply_fail("need VG UUID");
|
|
|
|
DEBUGLOG(s, "vg_remove: %s", vgid);
|
|
|
|
lock_pvid_to_vgid(s);
|
|
remove_metadata(s, vgid, 1);
|
|
unlock_pvid_to_vgid(s);
|
|
|
|
return daemon_reply_simple("OK", NULL);
|
|
}
|
|
|
|
static response set_global_info(lvmetad_state *s, request r)
|
|
{
|
|
const int global_invalid = daemon_request_int(r, "global_invalid", -1);
|
|
|
|
if (global_invalid == 1)
|
|
s->flags |= GLFL_INVALID;
|
|
|
|
else if (global_invalid == 0)
|
|
s->flags &= ~GLFL_INVALID;
|
|
|
|
return daemon_reply_simple("OK", NULL);
|
|
}
|
|
|
|
static response get_global_info(lvmetad_state *s, request r)
|
|
{
|
|
return daemon_reply_simple("OK", "global_invalid = %d",
|
|
(s->flags & GLFL_INVALID) ? 1 : 0,
|
|
NULL);
|
|
}
|
|
|
|
static response set_vg_info(lvmetad_state *s, request r)
|
|
{
|
|
struct dm_config_tree *vg;
|
|
struct vg_info *info;
|
|
const char *name;
|
|
const char *uuid;
|
|
const int64_t new_version = daemon_request_int(r, "version", -1);
|
|
int64_t cache_version;
|
|
|
|
if (new_version == -1)
|
|
goto out;
|
|
|
|
if (!(uuid = daemon_request_str(r, "uuid", NULL)))
|
|
goto use_name;
|
|
|
|
if ((vg = dm_hash_lookup(s->vgid_to_metadata, uuid)))
|
|
goto vers;
|
|
use_name:
|
|
if (!(name = daemon_request_str(r, "name", NULL)))
|
|
goto out;
|
|
|
|
if (!(uuid = dm_hash_lookup(s->vgname_to_vgid, name)))
|
|
goto out;
|
|
|
|
if (!(vg = dm_hash_lookup(s->vgid_to_metadata, uuid)))
|
|
goto out;
|
|
vers:
|
|
if (!new_version)
|
|
goto inval;
|
|
|
|
cache_version = dm_config_find_int64(vg->root, "metadata/seqno", -1);
|
|
|
|
if (cache_version != -1 && new_version != -1 && cache_version >= new_version)
|
|
goto out;
|
|
inval:
|
|
info = dm_hash_lookup(s->vgid_to_info, uuid);
|
|
if (!info) {
|
|
info = malloc(sizeof(struct vg_info));
|
|
if (!info)
|
|
goto bad;
|
|
memset(info, 0, sizeof(struct vg_info));
|
|
if (!dm_hash_insert(s->vgid_to_info, uuid, (void*)info))
|
|
goto bad;
|
|
}
|
|
|
|
info->external_version = new_version;
|
|
info->flags |= VGFL_INVALID;
|
|
|
|
out:
|
|
return daemon_reply_simple("OK", NULL);
|
|
bad:
|
|
return reply_fail("out of memory");
|
|
}
|
|
|
|
static void _dump_cft(struct buffer *buf, struct dm_hash_table *ht, const char *key_addr)
|
|
{
|
|
struct dm_hash_node *n;
|
|
|
|
dm_hash_iterate(n, ht) {
|
|
struct dm_config_tree *cft = dm_hash_get_data(ht, n);
|
|
const char *key_backup = cft->root->key;
|
|
cft->root->key = dm_config_find_str(cft->root, key_addr, "unknown");
|
|
(void) dm_config_write_node(cft->root, buffer_line, buf);
|
|
cft->root->key = key_backup;
|
|
}
|
|
}
|
|
|
|
static void _dump_pairs(struct buffer *buf, struct dm_hash_table *ht, const char *name, int int_key)
|
|
{
|
|
char *append;
|
|
struct dm_hash_node *n;
|
|
|
|
buffer_append(buf, name);
|
|
buffer_append(buf, " {\n");
|
|
|
|
dm_hash_iterate(n, ht) {
|
|
const char *key = dm_hash_get_key(ht, n),
|
|
*val = dm_hash_get_data(ht, n);
|
|
buffer_append(buf, " ");
|
|
if (int_key)
|
|
(void) dm_asprintf(&append, "%d = \"%s\"", *(const int*)key, val);
|
|
else
|
|
(void) dm_asprintf(&append, "%s = \"%s\"", key, val);
|
|
if (append)
|
|
buffer_append(buf, append);
|
|
buffer_append(buf, "\n");
|
|
dm_free(append);
|
|
}
|
|
buffer_append(buf, "}\n");
|
|
}
|
|
|
|
static void _dump_info_version(struct buffer *buf, struct dm_hash_table *ht, const char *name, int int_key)
|
|
{
|
|
char *append;
|
|
struct dm_hash_node *n = dm_hash_get_first(ht);
|
|
struct vg_info *info;
|
|
|
|
buffer_append(buf, name);
|
|
buffer_append(buf, " {\n");
|
|
|
|
while (n) {
|
|
const char *key = dm_hash_get_key(ht, n);
|
|
info = dm_hash_get_data(ht, n);
|
|
buffer_append(buf, " ");
|
|
(void) dm_asprintf(&append, "%s = %lld", key, (long long)info->external_version);
|
|
if (append)
|
|
buffer_append(buf, append);
|
|
buffer_append(buf, "\n");
|
|
dm_free(append);
|
|
n = dm_hash_get_next(ht, n);
|
|
}
|
|
buffer_append(buf, "}\n");
|
|
}
|
|
|
|
static void _dump_info_flags(struct buffer *buf, struct dm_hash_table *ht, const char *name, int int_key)
|
|
{
|
|
char *append;
|
|
struct dm_hash_node *n = dm_hash_get_first(ht);
|
|
struct vg_info *info;
|
|
|
|
buffer_append(buf, name);
|
|
buffer_append(buf, " {\n");
|
|
|
|
while (n) {
|
|
const char *key = dm_hash_get_key(ht, n);
|
|
info = dm_hash_get_data(ht, n);
|
|
buffer_append(buf, " ");
|
|
(void) dm_asprintf(&append, "%s = %llx", key, (long long)info->flags);
|
|
if (append)
|
|
buffer_append(buf, append);
|
|
buffer_append(buf, "\n");
|
|
dm_free(append);
|
|
n = dm_hash_get_next(ht, n);
|
|
}
|
|
buffer_append(buf, "}\n");
|
|
}
|
|
|
|
static response dump(lvmetad_state *s)
|
|
{
|
|
response res = { 0 };
|
|
struct buffer *b = &res.buffer;
|
|
|
|
buffer_init(b);
|
|
|
|
/* Lock everything so that we get a consistent dump. */
|
|
|
|
lock_vgid_to_metadata(s);
|
|
lock_pvid_to_pvmeta(s);
|
|
lock_pvid_to_vgid(s);
|
|
|
|
buffer_append(b, "# VG METADATA\n\n");
|
|
_dump_cft(b, s->vgid_to_metadata, "metadata/id");
|
|
|
|
buffer_append(b, "\n# PV METADATA\n\n");
|
|
_dump_cft(b, s->pvid_to_pvmeta, "pvmeta/id");
|
|
|
|
buffer_append(b, "\n# VGID to VGNAME mapping\n\n");
|
|
_dump_pairs(b, s->vgid_to_vgname, "vgid_to_vgname", 0);
|
|
|
|
buffer_append(b, "\n# VGID to outdated PVs mapping\n\n");
|
|
_dump_cft(b, s->vgid_to_outdated_pvs, "outdated_pvs/vgid");
|
|
|
|
buffer_append(b, "\n# VGNAME to VGID mapping\n\n");
|
|
_dump_pairs(b, s->vgname_to_vgid, "vgname_to_vgid", 0);
|
|
|
|
buffer_append(b, "\n# PVID to VGID mapping\n\n");
|
|
_dump_pairs(b, s->pvid_to_vgid, "pvid_to_vgid", 0);
|
|
|
|
buffer_append(b, "\n# DEVICE to PVID mapping\n\n");
|
|
_dump_pairs(b, s->device_to_pvid, "device_to_pvid", 1);
|
|
|
|
buffer_append(b, "\n# VGID to INFO version mapping\n\n");
|
|
_dump_info_version(b, s->vgid_to_info, "vgid_to_info", 0);
|
|
|
|
buffer_append(b, "\n# VGID to INFO flags mapping\n\n");
|
|
_dump_info_flags(b, s->vgid_to_info, "vgid_to_info", 0);
|
|
|
|
unlock_pvid_to_vgid(s);
|
|
unlock_pvid_to_pvmeta(s);
|
|
unlock_vgid_to_metadata(s);
|
|
|
|
return res;
|
|
}
|
|
|
|
static response handler(daemon_state s, client_handle h, request r)
|
|
{
|
|
lvmetad_state *state = s.private;
|
|
const char *rq = daemon_request_str(r, "request", "NONE");
|
|
const char *token = daemon_request_str(r, "token", "NONE");
|
|
|
|
pthread_mutex_lock(&state->token_lock);
|
|
if (!strcmp(rq, "token_update")) {
|
|
strncpy(state->token, token, 128);
|
|
state->token[127] = 0;
|
|
pthread_mutex_unlock(&state->token_lock);
|
|
return daemon_reply_simple("OK", NULL);
|
|
}
|
|
|
|
if (strcmp(token, state->token) && strcmp(rq, "dump") && strcmp(token, "skip")) {
|
|
pthread_mutex_unlock(&state->token_lock);
|
|
return daemon_reply_simple("token_mismatch",
|
|
"expected = %s", state->token,
|
|
"received = %s", token,
|
|
"reason = %s",
|
|
"lvmetad cache is invalid due to a global_filter change or due to a running rescan", NULL);
|
|
}
|
|
pthread_mutex_unlock(&state->token_lock);
|
|
|
|
/*
|
|
* TODO Add a stats call, with transaction count/rate, time since last
|
|
* update &c.
|
|
*/
|
|
if (!strcmp(rq, "pv_found"))
|
|
return pv_found(state, r);
|
|
|
|
if (!strcmp(rq, "pv_gone"))
|
|
return pv_gone(state, r);
|
|
|
|
if (!strcmp(rq, "pv_clear_all"))
|
|
return pv_clear_all(state, r);
|
|
|
|
if (!strcmp(rq, "pv_lookup"))
|
|
return pv_lookup(state, r);
|
|
|
|
if (!strcmp(rq, "vg_update"))
|
|
return vg_update(state, r);
|
|
|
|
if (!strcmp(rq, "vg_clear_outdated_pvs"))
|
|
return vg_clear_outdated_pvs(state, r);
|
|
|
|
if (!strcmp(rq, "vg_remove"))
|
|
return vg_remove(state, r);
|
|
|
|
if (!strcmp(rq, "vg_lookup"))
|
|
return vg_lookup(state, r);
|
|
|
|
if (!strcmp(rq, "pv_list"))
|
|
return pv_list(state, r);
|
|
|
|
if (!strcmp(rq, "vg_list"))
|
|
return vg_list(state, r);
|
|
|
|
if (!strcmp(rq, "set_global_info"))
|
|
return set_global_info(state, r);
|
|
|
|
if (!strcmp(rq, "get_global_info"))
|
|
return get_global_info(state, r);
|
|
|
|
if (!strcmp(rq, "set_vg_info"))
|
|
return set_vg_info(state, r);
|
|
|
|
if (!strcmp(rq, "dump"))
|
|
return dump(state);
|
|
|
|
return reply_fail("request not implemented");
|
|
}
|
|
|
|
static int init(daemon_state *s)
|
|
{
|
|
pthread_mutexattr_t rec;
|
|
lvmetad_state *ls = s->private;
|
|
ls->log = s->log;
|
|
|
|
pthread_mutexattr_init(&rec);
|
|
pthread_mutexattr_settype(&rec, PTHREAD_MUTEX_RECURSIVE_NP);
|
|
pthread_mutex_init(&ls->lock.pvid_to_pvmeta, &rec);
|
|
pthread_mutex_init(&ls->lock.vgid_to_metadata, &rec);
|
|
pthread_mutex_init(&ls->lock.pvid_to_vgid, NULL);
|
|
pthread_mutex_init(&ls->lock.vg_lock_map, NULL);
|
|
pthread_mutex_init(&ls->token_lock, NULL);
|
|
create_metadata_hashes(ls);
|
|
|
|
ls->lock.vg = dm_hash_create(32);
|
|
ls->token[0] = 0;
|
|
|
|
/* Set up stderr logging depending on the -l option. */
|
|
if (!daemon_log_parse(ls->log, DAEMON_LOG_OUTLET_STDERR, ls->log_config, 1))
|
|
return 0;
|
|
|
|
DEBUGLOG(s, "initialised state: vgid_to_metadata = %p", ls->vgid_to_metadata);
|
|
if (!ls->pvid_to_vgid || !ls->vgid_to_metadata)
|
|
return 0;
|
|
|
|
/* if (ls->initial_registrations)
|
|
_process_initial_registrations(ds->initial_registrations); */
|
|
|
|
if (ls->idle)
|
|
ls->idle->is_idle = 1;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int fini(daemon_state *s)
|
|
{
|
|
lvmetad_state *ls = s->private;
|
|
struct dm_hash_node *n;
|
|
|
|
DEBUGLOG(s, "fini");
|
|
|
|
destroy_metadata_hashes(ls);
|
|
|
|
/* Destroy the lock hashes now. */
|
|
dm_hash_iterate(n, ls->lock.vg) {
|
|
pthread_mutex_destroy(dm_hash_get_data(ls->lock.vg, n));
|
|
free(dm_hash_get_data(ls->lock.vg, n));
|
|
}
|
|
|
|
dm_hash_destroy(ls->lock.vg);
|
|
return 1;
|
|
}
|
|
|
|
static int process_timeout_arg(const char *str, unsigned *max_timeouts)
|
|
{
|
|
char *endptr;
|
|
unsigned long l;
|
|
|
|
errno = 0;
|
|
l = strtoul(str, &endptr, 10);
|
|
if (errno || *endptr || l >= UINT_MAX)
|
|
return 0;
|
|
|
|
*max_timeouts = (unsigned) l;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void usage(const char *prog, FILE *file)
|
|
{
|
|
fprintf(file, "Usage:\n"
|
|
"%s [-V] [-h] [-f] [-l level[,level ...]] [-s path] [-t secs]\n\n"
|
|
" -V Show version of lvmetad\n"
|
|
" -h Show this help information\n"
|
|
" -f Don't fork, run in the foreground\n"
|
|
" -l Logging message levels (all,fatal,error,warn,info,wire,debug)\n"
|
|
" -p Set path to the pidfile\n"
|
|
" -s Set path to the socket to listen on\n"
|
|
" -t Time to wait in seconds before shutdown on idle (missing or 0 = inifinite)\n\n", prog);
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
signed char opt;
|
|
struct timeval timeout;
|
|
daemon_idle di = { .ptimeout = &timeout };
|
|
lvmetad_state ls = { .log_config = "" };
|
|
daemon_state s = {
|
|
.daemon_fini = fini,
|
|
.daemon_init = init,
|
|
.handler = handler,
|
|
.name = "lvmetad",
|
|
.pidfile = getenv("LVM_LVMETAD_PIDFILE") ? : LVMETAD_PIDFILE,
|
|
.private = &ls,
|
|
.protocol = "lvmetad",
|
|
.protocol_version = 1,
|
|
.socket_path = getenv("LVM_LVMETAD_SOCKET") ? : LVMETAD_SOCKET,
|
|
};
|
|
|
|
// use getopt_long
|
|
while ((opt = getopt(argc, argv, "?fhVl:p:s:t:")) != EOF) {
|
|
switch (opt) {
|
|
case 'h':
|
|
usage(argv[0], stdout);
|
|
exit(0);
|
|
case '?':
|
|
usage(argv[0], stderr);
|
|
exit(0);
|
|
case 'f':
|
|
s.foreground = 1;
|
|
break;
|
|
case 'l':
|
|
ls.log_config = optarg;
|
|
break;
|
|
case 'p':
|
|
s.pidfile = optarg;
|
|
break;
|
|
case 's': // --socket
|
|
s.socket_path = optarg;
|
|
break;
|
|
case 't':
|
|
if (!process_timeout_arg(optarg, &di.max_timeouts)) {
|
|
fprintf(stderr, "Invalid value of timeout parameter.\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
/* 0 equals to wait indefinitely */
|
|
if (di.max_timeouts)
|
|
s.idle = ls.idle = &di;
|
|
break;
|
|
case 'V':
|
|
printf("lvmetad version: " LVM_VERSION "\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
daemon_start(s);
|
|
|
|
return 0;
|
|
}
|