mirror of
git://sourceware.org/git/lvm2.git
synced 2024-12-22 17:35:59 +03:00
3329bbfdf3
clvmd-gulm unlocks VG & orphan locks at startup in case they are stale. clvmd-gulm now unlocks VG & orphan locks if client dies.
533 lines
12 KiB
C
533 lines
12 KiB
C
/*
|
|
* Copyright (C) 2002-2004 Sistina Software, Inc. All rights reserved.
|
|
* Copyright (C) 2004 Red Hat, Inc. All rights reserved.
|
|
*
|
|
* This file is part of LVM2.
|
|
*
|
|
* This copyrighted material is made available to anyone wishing to use,
|
|
* modify, copy, or redistribute it subject to the terms and conditions
|
|
* of the GNU General Public License v.2.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include <pthread.h>
|
|
#include <sys/types.h>
|
|
#include <sys/utsname.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <syslog.h>
|
|
#include <assert.h>
|
|
|
|
#include "libdlm.h"
|
|
#include "clvm.h"
|
|
#include "clvmd-comms.h"
|
|
#include "clvmd.h"
|
|
#include "lvm-functions.h"
|
|
|
|
/* LVM2 headers */
|
|
#include "toolcontext.h"
|
|
#include "log.h"
|
|
#include "activate.h"
|
|
#include "hash.h"
|
|
#include "locking.h"
|
|
|
|
static struct cmd_context *cmd = NULL;
|
|
static struct hash_table *lv_hash = NULL;
|
|
static pthread_mutex_t lv_hash_lock;
|
|
|
|
struct lv_info {
|
|
int lock_id;
|
|
int lock_mode;
|
|
};
|
|
|
|
/* Return the mode a lock is currently held at (or -1 if not held) */
|
|
static int get_current_lock(char *resource)
|
|
{
|
|
struct lv_info *lvi;
|
|
|
|
pthread_mutex_lock(&lv_hash_lock);
|
|
lvi = hash_lookup(lv_hash, resource);
|
|
pthread_mutex_unlock(&lv_hash_lock);
|
|
if (lvi) {
|
|
return lvi->lock_mode;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* Called at shutdown to tidy the lockspace */
|
|
void unlock_all()
|
|
{
|
|
struct hash_node *v;
|
|
|
|
pthread_mutex_lock(&lv_hash_lock);
|
|
hash_iterate(v, lv_hash) {
|
|
struct lv_info *lvi = hash_get_data(lv_hash, v);
|
|
|
|
sync_unlock(hash_get_key(lv_hash, v), lvi->lock_id);
|
|
}
|
|
pthread_mutex_unlock(&lv_hash_lock);
|
|
}
|
|
|
|
/* Gets a real lock and keeps the info in the hash table */
|
|
int hold_lock(char *resource, int mode, int flags)
|
|
{
|
|
int status;
|
|
int saved_errno;
|
|
struct lv_info *lvi;
|
|
|
|
flags &= LKF_NOQUEUE; /* Only LKF_NOQUEUE is valid here */
|
|
|
|
pthread_mutex_lock(&lv_hash_lock);
|
|
lvi = hash_lookup(lv_hash, resource);
|
|
pthread_mutex_unlock(&lv_hash_lock);
|
|
if (lvi) {
|
|
/* Already exists - convert it */
|
|
status =
|
|
sync_lock(resource, mode, LKF_CONVERT | flags,
|
|
&lvi->lock_id);
|
|
saved_errno = errno;
|
|
if (!status)
|
|
lvi->lock_mode = mode;
|
|
|
|
if (status) {
|
|
DEBUGLOG("hold_lock. convert to %d failed: %s\n", mode,
|
|
strerror(errno));
|
|
}
|
|
errno = saved_errno;
|
|
} else {
|
|
lvi = malloc(sizeof(struct lv_info));
|
|
if (!lvi)
|
|
return -1;
|
|
|
|
lvi->lock_mode = mode;
|
|
status = sync_lock(resource, mode, flags, &lvi->lock_id);
|
|
saved_errno = errno;
|
|
if (status) {
|
|
free(lvi);
|
|
DEBUGLOG("hold_lock. lock at %d failed: %s\n", mode,
|
|
strerror(errno));
|
|
} else {
|
|
pthread_mutex_lock(&lv_hash_lock);
|
|
hash_insert(lv_hash, resource, lvi);
|
|
pthread_mutex_unlock(&lv_hash_lock);
|
|
}
|
|
errno = saved_errno;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
/* Unlock and remove it from the hash table */
|
|
int hold_unlock(char *resource)
|
|
{
|
|
struct lv_info *lvi;
|
|
int status;
|
|
int saved_errno;
|
|
|
|
pthread_mutex_lock(&lv_hash_lock);
|
|
lvi = hash_lookup(lv_hash, resource);
|
|
pthread_mutex_unlock(&lv_hash_lock);
|
|
if (!lvi) {
|
|
DEBUGLOG("hold_unlock, lock not already held\n");
|
|
return 0;
|
|
}
|
|
|
|
status = sync_unlock(resource, lvi->lock_id);
|
|
saved_errno = errno;
|
|
if (!status) {
|
|
pthread_mutex_lock(&lv_hash_lock);
|
|
hash_remove(lv_hash, resource);
|
|
pthread_mutex_unlock(&lv_hash_lock);
|
|
free(lvi);
|
|
} else {
|
|
DEBUGLOG("hold_unlock. unlock failed(%d): %s\n", status,
|
|
strerror(errno));
|
|
}
|
|
|
|
errno = saved_errno;
|
|
return status;
|
|
}
|
|
|
|
/* Watch the return codes here.
|
|
liblvm API functions return 1(true) for success, 0(false) for failure and don't set errno.
|
|
libdlm API functions return 0 for success, -1 for failure and do set errno.
|
|
These functions here return 0 for success or >0 for failure (where the retcode is errno)
|
|
*/
|
|
|
|
/* Activate LV exclusive or non-exclusive */
|
|
static int do_activate_lv(char *resource, int mode)
|
|
{
|
|
int oldmode;
|
|
int status;
|
|
int activate_lv;
|
|
struct lvinfo lvi;
|
|
|
|
/* Is it already open ? */
|
|
oldmode = get_current_lock(resource);
|
|
if (oldmode == mode) {
|
|
return 0; /* Nothing to do */
|
|
}
|
|
|
|
/* Does the config file want us to activate this LV ? */
|
|
if (!lv_activation_filter(cmd, resource, &activate_lv))
|
|
return EIO;
|
|
|
|
if (!activate_lv)
|
|
return 0; /* Success, we did nothing! */
|
|
|
|
/* Do we need to activate exclusively? */
|
|
if (activate_lv == 2)
|
|
mode = LKM_EXMODE;
|
|
|
|
/* OK, try to get the lock */
|
|
status = hold_lock(resource, mode, LKF_NOQUEUE);
|
|
if (status)
|
|
return errno;
|
|
|
|
/* If it's suspended then resume it */
|
|
if (!lv_info_by_lvid(cmd, resource, &lvi, 0))
|
|
return EIO;
|
|
|
|
if (lvi.suspended)
|
|
if (!lv_resume(cmd, resource))
|
|
return EIO;
|
|
|
|
/* Now activate it */
|
|
if (!lv_activate(cmd, resource))
|
|
return EIO;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Resume the LV if it was active */
|
|
static int do_resume_lv(char *resource)
|
|
{
|
|
int oldmode;
|
|
|
|
/* Is it open ? */
|
|
oldmode = get_current_lock(resource);
|
|
if (oldmode == -1) {
|
|
DEBUGLOG("do_deactivate_lock, lock not already held\n");
|
|
return 0; /* We don't need to do anything */
|
|
}
|
|
|
|
if (!lv_resume_if_active(cmd, resource))
|
|
return EIO;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Suspend the device if active */
|
|
static int do_suspend_lv(char *resource)
|
|
{
|
|
int oldmode;
|
|
struct lvinfo lvi;
|
|
|
|
/* Is it open ? */
|
|
oldmode = get_current_lock(resource);
|
|
if (oldmode == -1) {
|
|
DEBUGLOG("do_suspend_lv, lock held at %d\n", oldmode);
|
|
return 0; /* Not active, so it's OK */
|
|
}
|
|
|
|
/* Only suspend it if it exists */
|
|
if (!lv_info_by_lvid(cmd, resource, &lvi, 0))
|
|
return EIO;
|
|
|
|
if (lvi.exists) {
|
|
if (!lv_suspend_if_active(cmd, resource)) {
|
|
return EIO;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int do_deactivate_lv(char *resource)
|
|
{
|
|
int oldmode;
|
|
int status;
|
|
|
|
/* Is it open ? */
|
|
oldmode = get_current_lock(resource);
|
|
if (oldmode == -1) {
|
|
DEBUGLOG("do_deactivate_lock, lock not already held\n");
|
|
return 0; /* We don't need to do anything */
|
|
}
|
|
|
|
if (!lv_deactivate(cmd, resource))
|
|
return EIO;
|
|
|
|
status = hold_unlock(resource);
|
|
if (status)
|
|
return errno;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* This is the LOCK_LV part that happens on all nodes in the cluster -
|
|
it is responsible for the interaction with device-mapper and LVM */
|
|
int do_lock_lv(unsigned char command, unsigned char lock_flags, char *resource)
|
|
{
|
|
int status = 0;
|
|
|
|
DEBUGLOG("do_lock_lv: resource '%s', cmd = 0x%x, flags = %d\n",
|
|
resource, command, lock_flags);
|
|
|
|
if (!cmd->config_valid || config_files_changed(cmd)) {
|
|
/* Reinitialise various settings inc. logging, filters */
|
|
if (!refresh_toolcontext(cmd)) {
|
|
log_error("Updated config file invalid. Aborting.");
|
|
return EINVAL;
|
|
}
|
|
}
|
|
|
|
switch (command) {
|
|
case LCK_LV_EXCLUSIVE:
|
|
status = do_activate_lv(resource, LKM_EXMODE);
|
|
break;
|
|
|
|
case LCK_LV_SUSPEND:
|
|
status = do_suspend_lv(resource);
|
|
break;
|
|
|
|
case LCK_UNLOCK:
|
|
case LCK_LV_RESUME: /* if active */
|
|
status = do_resume_lv(resource);
|
|
break;
|
|
|
|
case LCK_LV_ACTIVATE:
|
|
status = do_activate_lv(resource, LKM_CRMODE);
|
|
break;
|
|
|
|
case LCK_LV_DEACTIVATE:
|
|
status = do_deactivate_lv(resource);
|
|
break;
|
|
|
|
default:
|
|
DEBUGLOG("Invalid LV command 0x%x\n", command);
|
|
status = EINVAL;
|
|
break;
|
|
}
|
|
|
|
/* clean the pool for another command */
|
|
pool_empty(cmd->mem);
|
|
|
|
DEBUGLOG("Command return is %d\n", status);
|
|
return status;
|
|
}
|
|
|
|
/* Functions to do on the local node only BEFORE the cluster-wide stuff above happens */
|
|
int pre_lock_lv(unsigned char command, unsigned char lock_flags, char *resource)
|
|
{
|
|
/* Nearly all the stuff happens cluster-wide. Apart from SUSPEND. Here we get the
|
|
lock out on this node (because we are the node modifying the metadata)
|
|
before suspending cluster-wide.
|
|
*/
|
|
if (command == LCK_LV_SUSPEND) {
|
|
DEBUGLOG("pre_lock_lv: resource '%s', cmd = 0x%x, flags = %d\n",
|
|
resource, command, lock_flags);
|
|
|
|
if (hold_lock(resource, LKM_PWMODE, LKF_NOQUEUE))
|
|
return errno;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Functions to do on the local node only AFTER the cluster-wide stuff above happens */
|
|
int post_lock_lv(unsigned char command, unsigned char lock_flags,
|
|
char *resource)
|
|
{
|
|
/* Opposite of above, done on resume after a metadata update */
|
|
if (command == LCK_LV_RESUME) {
|
|
int oldmode;
|
|
|
|
DEBUGLOG
|
|
("post_lock_lv: resource '%s', cmd = 0x%x, flags = %d\n",
|
|
resource, command, lock_flags);
|
|
|
|
/* If the lock state is PW then restore it to what it was */
|
|
oldmode = get_current_lock(resource);
|
|
if (oldmode == LKM_PWMODE) {
|
|
struct lvinfo lvi;
|
|
|
|
if (!lv_info_by_lvid(cmd, resource, &lvi, 0))
|
|
return EIO;
|
|
|
|
if (lvi.exists) {
|
|
if (hold_lock(resource, LKM_CRMODE, 0))
|
|
return errno;
|
|
} else {
|
|
if (hold_unlock(resource))
|
|
return errno;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Check if a VG is un use by LVM1 so we don't stomp on it */
|
|
int do_check_lvm1(char *vgname)
|
|
{
|
|
int status;
|
|
|
|
status = check_lvm1_vg_inactive(cmd, vgname);
|
|
|
|
return status == 1 ? 0 : EBUSY;
|
|
}
|
|
|
|
|
|
/* Only called at gulm startup. Drop any leftover VG or P_orphan locks
|
|
that might be hanging around if we died for any reason
|
|
*/
|
|
static void drop_vg_locks()
|
|
{
|
|
char vg[128];
|
|
char line[255];
|
|
FILE *vgs =
|
|
popen
|
|
("lvm pvs --nolocking --noheadings -o vg_name", "r");
|
|
|
|
sync_unlock("P_orphans", LCK_EXCL);
|
|
|
|
if (!vgs)
|
|
return;
|
|
|
|
while (fgets(line, sizeof(line), vgs)) {
|
|
char *vgend;
|
|
char *vgstart;
|
|
|
|
if (line[strlen(line)-1] == '\n')
|
|
line[strlen(line)-1] = '\0';
|
|
|
|
vgstart = line + strspn(line, " ");
|
|
vgend = vgstart + strcspn(vgstart, " ");
|
|
*vgend = '\0';
|
|
|
|
if (strncmp(vgstart, "WARNING:", 8) == 0)
|
|
continue;
|
|
|
|
sprintf(vg, "V_%s", vgstart);
|
|
sync_unlock(vg, LCK_EXCL);
|
|
|
|
}
|
|
fclose(vgs);
|
|
}
|
|
|
|
/*
|
|
* Ideally, clvmd should be started before any LVs are active
|
|
* but this may not be the case...
|
|
* I suppose this also comes in handy if clvmd crashes, not that it would!
|
|
*/
|
|
static void *get_initial_state()
|
|
{
|
|
char lv[64], vg[64], flags[25];
|
|
char uuid[65];
|
|
char line[255];
|
|
FILE *lvs =
|
|
popen
|
|
("lvm lvs --nolocking --noheadings -o vg_uuid,lv_uuid,lv_attr",
|
|
"r");
|
|
|
|
if (!lvs)
|
|
return NULL;
|
|
|
|
while (fgets(line, sizeof(line), lvs)) {
|
|
if (sscanf(line, "%s %s %s\n", vg, lv, flags) == 3) {
|
|
|
|
/* States: s:suspended a:active S:dropped snapshot I:invalid snapshot */
|
|
if (strlen(vg) == 38 && /* is is a valid UUID ? */
|
|
(flags[4] == 'a' || flags[4] == 's')) { /* is it active or suspended? */
|
|
/* Convert hyphen-separated UUIDs into one */
|
|
memcpy(&uuid[0], &vg[0], 6);
|
|
memcpy(&uuid[6], &vg[7], 4);
|
|
memcpy(&uuid[10], &vg[12], 4);
|
|
memcpy(&uuid[14], &vg[17], 4);
|
|
memcpy(&uuid[18], &vg[22], 4);
|
|
memcpy(&uuid[22], &vg[27], 4);
|
|
memcpy(&uuid[26], &vg[32], 6);
|
|
memcpy(&uuid[32], &lv[0], 6);
|
|
memcpy(&uuid[38], &lv[7], 4);
|
|
memcpy(&uuid[42], &lv[12], 4);
|
|
memcpy(&uuid[46], &lv[17], 4);
|
|
memcpy(&uuid[50], &lv[22], 4);
|
|
memcpy(&uuid[54], &lv[27], 4);
|
|
memcpy(&uuid[58], &lv[32], 6);
|
|
uuid[64] = '\0';
|
|
|
|
DEBUGLOG("getting initial lock for %s\n", uuid);
|
|
hold_lock(uuid, LKM_CRMODE, LKF_NOQUEUE);
|
|
}
|
|
}
|
|
}
|
|
fclose(lvs);
|
|
return NULL;
|
|
}
|
|
|
|
/* This checks some basic cluster-LVM configuration stuff */
|
|
static void check_config()
|
|
{
|
|
int locking_type;
|
|
|
|
locking_type = find_config_int(cmd->cft->root, "global/locking_type", 1);
|
|
|
|
if (locking_type == 3) /* compiled-in cluster support */
|
|
return;
|
|
|
|
if (locking_type == 2) { /* External library, check name */
|
|
const char *libname;
|
|
|
|
libname = find_config_str(cmd->cft->root, "global/locking_library",
|
|
"");
|
|
if (strstr(libname, "liblvm2clusterlock.so"))
|
|
return;
|
|
|
|
log_error("Incorrect LVM locking library specified in lvm.conf, cluster operations may not work.");
|
|
return;
|
|
}
|
|
log_error("locking_type not set correctly in lvm.conf, cluster operations will not work.");
|
|
}
|
|
|
|
void init_lvhash()
|
|
{
|
|
/* Create hash table for keeping LV locks & status */
|
|
lv_hash = hash_create(100);
|
|
pthread_mutex_init(&lv_hash_lock, NULL);
|
|
}
|
|
|
|
/* Called to initialise the LVM context of the daemon */
|
|
int init_lvm(int using_gulm)
|
|
{
|
|
if (!(cmd = create_toolcontext(NULL))) {
|
|
log_error("Failed to allocate command context");
|
|
return 0;
|
|
}
|
|
|
|
/* Use LOG_DAEMON for syslog messages instead of LOG_USER */
|
|
init_syslog(LOG_DAEMON);
|
|
init_debug(_LOG_ERR);
|
|
|
|
/* Check lvm.conf is setup for cluster-LVM */
|
|
check_config();
|
|
|
|
/* Remove any non-LV locks that may have been left around */
|
|
if (using_gulm)
|
|
drop_vg_locks();
|
|
|
|
get_initial_state();
|
|
|
|
return 1;
|
|
}
|