/*
* lock_driver_lockd.c: A lock driver which locks nothing
*
* Copyright (C) 2010-2011, 2014 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; If not, see
* .
*
*/
#include
#include "lock_driver.h"
#include "virconf.h"
#include "viralloc.h"
#include "vircommand.h"
#include "vircrypto.h"
#include "virlog.h"
#include "viruuid.h"
#include "virfile.h"
#include "virerror.h"
#include "rpc/virnetclient.h"
#include "lock_protocol.h"
#include "configmake.h"
#include "virstoragefile.h"
#include "virstring.h"
#include "virutil.h"
#include "lock_driver_lockd.h"
#define VIR_FROM_THIS VIR_FROM_LOCKING
VIR_LOG_INIT("locking.lock_driver_lockd");
typedef struct _virLockManagerLockDaemonPrivate virLockManagerLockDaemonPrivate;
typedef virLockManagerLockDaemonPrivate *virLockManagerLockDaemonPrivatePtr;
typedef struct _virLockManagerLockDaemonResource virLockManagerLockDaemonResource;
typedef virLockManagerLockDaemonResource *virLockManagerLockDaemonResourcePtr;
typedef struct _virLockManagerLockDaemonDriver virLockManagerLockDaemonDriver;
typedef virLockManagerLockDaemonDriver *virLockManagerLockDaemonDriverPtr;
struct _virLockManagerLockDaemonResource {
char *lockspace;
char *name;
unsigned int flags;
};
struct _virLockManagerLockDaemonPrivate {
unsigned char uuid[VIR_UUID_BUFLEN];
char *name;
int id;
pid_t pid;
size_t nresources;
virLockManagerLockDaemonResourcePtr resources;
bool hasRWDisks;
};
struct _virLockManagerLockDaemonDriver {
bool autoDiskLease;
bool requireLeaseForDisks;
char *fileLockSpaceDir;
char *lvmLockSpaceDir;
char *scsiLockSpaceDir;
};
static virLockManagerLockDaemonDriverPtr driver;
static int virLockManagerLockDaemonLoadConfig(const char *configFile)
{
g_autoptr(virConf) conf = NULL;
if (access(configFile, R_OK) == -1) {
if (errno != ENOENT) {
virReportSystemError(errno,
_("Unable to access config file %s"),
configFile);
return -1;
}
return 0;
}
if (!(conf = virConfReadFile(configFile, 0)))
return -1;
if (virConfGetValueBool(conf, "auto_disk_leases", &driver->autoDiskLease) < 0)
return -1;
if (virConfGetValueString(conf, "file_lockspace_dir", &driver->fileLockSpaceDir) < 0)
return -1;
if (virConfGetValueString(conf, "lvm_lockspace_dir", &driver->lvmLockSpaceDir) < 0)
return -1;
if (virConfGetValueString(conf, "scsi_lockspace_dir", &driver->scsiLockSpaceDir) < 0)
return -1;
driver->requireLeaseForDisks = !driver->autoDiskLease;
if (virConfGetValueBool(conf, "require_lease_for_disks", &driver->requireLeaseForDisks) < 0)
return -1;
return 0;
}
static char *virLockManagerLockDaemonPath(bool privileged)
{
char *path;
if (privileged) {
path = g_strdup(RUNSTATEDIR "/libvirt/virtlockd-sock");
} else {
g_autofree char *rundir = NULL;
rundir = virGetUserRuntimeDirectory();
path = g_strdup_printf("%s/virtlockd-sock", rundir);
}
return path;
}
static int
virLockManagerLockDaemonConnectionRegister(virLockManagerPtr lock,
virNetClientPtr client,
virNetClientProgramPtr program,
int *counter)
{
virLockManagerLockDaemonPrivatePtr priv = lock->privateData;
virLockSpaceProtocolRegisterArgs args;
memset(&args, 0, sizeof(args));
args.flags = 0;
memcpy(args.owner.uuid, priv->uuid, VIR_UUID_BUFLEN);
args.owner.name = priv->name;
args.owner.id = priv->id;
args.owner.pid = priv->pid;
if (virNetClientProgramCall(program,
client,
(*counter)++,
VIR_LOCK_SPACE_PROTOCOL_PROC_REGISTER,
0, NULL, NULL, NULL,
(xdrproc_t)xdr_virLockSpaceProtocolRegisterArgs, (char*)&args,
(xdrproc_t)xdr_void, NULL) < 0)
return -1;
return 0;
}
static int
virLockManagerLockDaemonConnectionRestrict(virLockManagerPtr lock G_GNUC_UNUSED,
virNetClientPtr client,
virNetClientProgramPtr program,
int *counter)
{
virLockSpaceProtocolRestrictArgs args;
memset(&args, 0, sizeof(args));
args.flags = 0;
if (virNetClientProgramCall(program,
client,
(*counter)++,
VIR_LOCK_SPACE_PROTOCOL_PROC_RESTRICT,
0, NULL, NULL, NULL,
(xdrproc_t)xdr_virLockSpaceProtocolRestrictArgs, (char*)&args,
(xdrproc_t)xdr_void, NULL) < 0)
return -1;
return 0;
}
static virNetClientPtr virLockManagerLockDaemonConnectionNew(bool privileged,
virNetClientProgramPtr *prog)
{
virNetClientPtr client = NULL;
char *lockdpath;
char *daemonPath = NULL;
*prog = NULL;
if (!(lockdpath = virLockManagerLockDaemonPath(privileged)))
goto error;
if (!privileged &&
!(daemonPath = virFileFindResourceFull("virtlockd",
NULL, NULL,
abs_top_builddir "/src",
SBINDIR,
"VIRTLOCKD_PATH")))
goto error;
if (!(client = virNetClientNewUNIX(lockdpath,
daemonPath != NULL,
daemonPath)))
goto error;
if (!(*prog = virNetClientProgramNew(VIR_LOCK_SPACE_PROTOCOL_PROGRAM,
VIR_LOCK_SPACE_PROTOCOL_PROGRAM_VERSION,
NULL,
0,
NULL)))
goto error;
if (virNetClientAddProgram(client, *prog) < 0)
goto error;
VIR_FREE(daemonPath);
VIR_FREE(lockdpath);
return client;
error:
VIR_FREE(daemonPath);
VIR_FREE(lockdpath);
virNetClientClose(client);
virObjectUnref(client);
virObjectUnref(*prog);
return NULL;
}
static virNetClientPtr
virLockManagerLockDaemonConnect(virLockManagerPtr lock,
virNetClientProgramPtr *program,
int *counter)
{
virNetClientPtr client;
if (!(client = virLockManagerLockDaemonConnectionNew(geteuid() == 0, program)))
return NULL;
if (virLockManagerLockDaemonConnectionRegister(lock,
client,
*program,
counter) < 0)
goto error;
return client;
error:
virNetClientClose(client);
virObjectUnref(client);
return NULL;
}
static int virLockManagerLockDaemonSetupLockspace(const char *path)
{
virNetClientPtr client;
virNetClientProgramPtr program = NULL;
virLockSpaceProtocolCreateLockSpaceArgs args;
int rv = -1;
int counter = 0;
memset(&args, 0, sizeof(args));
args.path = (char*)path;
if (!(client = virLockManagerLockDaemonConnectionNew(geteuid() == 0, &program)))
return -1;
if (virNetClientProgramCall(program,
client,
counter++,
VIR_LOCK_SPACE_PROTOCOL_PROC_CREATE_LOCKSPACE,
0, NULL, NULL, NULL,
(xdrproc_t)xdr_virLockSpaceProtocolCreateLockSpaceArgs, (char*)&args,
(xdrproc_t)xdr_void, NULL) < 0) {
if (virGetLastErrorCode() == VIR_ERR_OPERATION_INVALID) {
/* The lockspace already exists */
virResetLastError();
rv = 0;
} else {
goto cleanup;
}
}
rv = 0;
cleanup:
virObjectUnref(program);
virNetClientClose(client);
virObjectUnref(client);
return rv;
}
static int virLockManagerLockDaemonDeinit(void);
static int virLockManagerLockDaemonInit(unsigned int version,
const char *configFile,
unsigned int flags)
{
VIR_DEBUG("version=%u configFile=%s flags=0x%x", version, NULLSTR(configFile), flags);
virCheckFlags(0, -1);
if (driver)
return 0;
driver = g_new0(virLockManagerLockDaemonDriver, 1);
driver->requireLeaseForDisks = true;
driver->autoDiskLease = true;
if (virLockManagerLockDaemonLoadConfig(configFile) < 0)
goto error;
if (driver->autoDiskLease) {
if (driver->fileLockSpaceDir &&
virLockManagerLockDaemonSetupLockspace(driver->fileLockSpaceDir) < 0)
goto error;
if (driver->lvmLockSpaceDir &&
virLockManagerLockDaemonSetupLockspace(driver->lvmLockSpaceDir) < 0)
goto error;
if (driver->scsiLockSpaceDir &&
virLockManagerLockDaemonSetupLockspace(driver->scsiLockSpaceDir) < 0)
goto error;
}
return 0;
error:
virLockManagerLockDaemonDeinit();
return -1;
}
static int virLockManagerLockDaemonDeinit(void)
{
if (!driver)
return 0;
VIR_FREE(driver->scsiLockSpaceDir);
VIR_FREE(driver->lvmLockSpaceDir);
VIR_FREE(driver->fileLockSpaceDir);
VIR_FREE(driver);
return 0;
}
static void
virLockManagerLockDaemonPrivateFree(virLockManagerLockDaemonPrivatePtr priv)
{
size_t i;
if (!priv)
return;
for (i = 0; i < priv->nresources; i++) {
VIR_FREE(priv->resources[i].lockspace);
VIR_FREE(priv->resources[i].name);
}
VIR_FREE(priv->resources);
VIR_FREE(priv->name);
VIR_FREE(priv);
}
static void virLockManagerLockDaemonFree(virLockManagerPtr lock)
{
if (!lock)
return;
virLockManagerLockDaemonPrivateFree(lock->privateData);
lock->privateData = NULL;
}
static int virLockManagerLockDaemonNew(virLockManagerPtr lock,
unsigned int type,
size_t nparams,
virLockManagerParamPtr params,
unsigned int flags)
{
virLockManagerLockDaemonPrivatePtr priv = NULL;
size_t i;
int ret = -1;
virCheckFlags(VIR_LOCK_MANAGER_NEW_STARTED, -1);
priv = g_new0(virLockManagerLockDaemonPrivate,
1);
switch (type) {
case VIR_LOCK_MANAGER_OBJECT_TYPE_DOMAIN:
for (i = 0; i < nparams; i++) {
if (STREQ(params[i].key, "uuid")) {
memcpy(priv->uuid, params[i].value.uuid, VIR_UUID_BUFLEN);
} else if (STREQ(params[i].key, "name")) {
priv->name = g_strdup(params[i].value.str);
} else if (STREQ(params[i].key, "id")) {
priv->id = params[i].value.iv;
} else if (STREQ(params[i].key, "pid")) {
priv->pid = params[i].value.iv;
} else if (STREQ(params[i].key, "uri")) {
/* ignored */
} else {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unexpected parameter %s for object"),
params[i].key);
goto cleanup;
}
}
if (priv->id == 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Missing ID parameter for domain object"));
goto cleanup;
}
if (priv->pid == 0)
VIR_DEBUG("Missing PID parameter for domain object");
if (!priv->name) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Missing name parameter for domain object"));
goto cleanup;
}
if (!virUUIDIsValid(priv->uuid)) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Missing UUID parameter for domain object"));
goto cleanup;
}
break;
default:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unknown lock manager object type %d"),
type);
goto cleanup;
}
lock->privateData = g_steal_pointer(&priv);
ret = 0;
cleanup:
virLockManagerLockDaemonPrivateFree(priv);
return ret;
}
#ifdef LVS
static int
virLockManagerGetLVMKey(const char *path,
char **key)
{
/*
* # lvs --noheadings --unbuffered --nosuffix --options "uuid" LVNAME
* 06UgP5-2rhb-w3Bo-3mdR-WeoL-pytO-SAa2ky
*/
int status;
int ret = -1;
g_autoptr(virCommand) cmd = NULL;
cmd = virCommandNewArgList(LVS, "--noheadings",
"--unbuffered", "--nosuffix",
"--options", "uuid", path,
NULL
);
*key = NULL;
/* Run the program and capture its output */
virCommandSetOutputBuffer(cmd, key);
if (virCommandRun(cmd, &status) < 0)
goto cleanup;
/* Explicitly check status == 0, rather than passing NULL
* to virCommandRun because we don't want to raise an actual
* error in this scenario, just return a NULL key.
*/
if (status == 0 && *key) {
char *nl;
char *tmp = *key;
/* Find first non-space character */
while (*tmp && g_ascii_isspace(*tmp))
tmp++;
/* Kill leading spaces */
if (tmp != *key)
memmove(*key, tmp, strlen(tmp)+1);
/* Kill trailing newline */
if ((nl = strchr(*key, '\n')))
*nl = '\0';
}
ret = 0;
cleanup:
if (*key && STREQ(*key, ""))
VIR_FREE(*key);
return ret;
}
#else
static int
virLockManagerGetLVMKey(const char *path,
char **key G_GNUC_UNUSED)
{
virReportSystemError(ENOSYS, _("Unable to get LVM key for %s"), path);
return -1;
}
#endif
static int virLockManagerLockDaemonAddResource(virLockManagerPtr lock,
unsigned int type,
const char *name,
size_t nparams,
virLockManagerParamPtr params,
unsigned int flags)
{
virLockManagerLockDaemonPrivatePtr priv = lock->privateData;
char *newName = NULL;
char *newLockspace = NULL;
bool autoCreate = false;
int ret = -1;
virCheckFlags(VIR_LOCK_MANAGER_RESOURCE_READONLY |
VIR_LOCK_MANAGER_RESOURCE_SHARED, -1);
if (flags & VIR_LOCK_MANAGER_RESOURCE_READONLY)
return 0;
switch (type) {
case VIR_LOCK_MANAGER_RESOURCE_TYPE_DISK:
if (params || nparams) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Unexpected parameters for disk resource"));
goto cleanup;
}
if (!driver->autoDiskLease) {
if (!(flags & (VIR_LOCK_MANAGER_RESOURCE_SHARED |
VIR_LOCK_MANAGER_RESOURCE_READONLY)))
priv->hasRWDisks = true;
return 0;
}
/* XXX we should somehow pass in TYPE=BLOCK info
* from the domain_lock code, instead of assuming /dev
*/
if (STRPREFIX(name, "/dev") &&
driver->lvmLockSpaceDir) {
VIR_DEBUG("Trying to find an LVM UUID for %s", name);
if (virLockManagerGetLVMKey(name, &newName) < 0)
goto cleanup;
if (newName) {
VIR_DEBUG("Got an LVM UUID %s for %s", newName, name);
newLockspace = g_strdup(driver->lvmLockSpaceDir);
autoCreate = true;
break;
}
virResetLastError();
/* Fallback to generic non-block code */
}
if (STRPREFIX(name, "/dev") &&
driver->scsiLockSpaceDir) {
VIR_DEBUG("Trying to find an SCSI ID for %s", name);
if (virStorageFileGetSCSIKey(name, &newName, false) < 0)
goto cleanup;
if (newName) {
VIR_DEBUG("Got an SCSI ID %s for %s", newName, name);
newLockspace = g_strdup(driver->scsiLockSpaceDir);
autoCreate = true;
break;
}
virResetLastError();
/* Fallback to generic non-block code */
}
if (driver->fileLockSpaceDir) {
newLockspace = g_strdup(driver->fileLockSpaceDir);
if (virCryptoHashString(VIR_CRYPTO_HASH_SHA256, name, &newName) < 0)
goto cleanup;
autoCreate = true;
VIR_DEBUG("Using indirect lease %s for %s", newName, name);
} else {
newLockspace = g_strdup("");
newName = g_strdup(name);
VIR_DEBUG("Using direct lease for %s", name);
}
break;
case VIR_LOCK_MANAGER_RESOURCE_TYPE_LEASE: {
size_t i;
char *path = NULL;
char *lockspace = NULL;
for (i = 0; i < nparams; i++) {
if (STREQ(params[i].key, "offset")) {
if (params[i].value.ul != 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Offset must be zero for this lock manager"));
goto cleanup;
}
} else if (STREQ(params[i].key, "lockspace")) {
lockspace = params[i].value.str;
} else if (STREQ(params[i].key, "path")) {
path = params[i].value.str;
} else {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unexpected parameter %s for lease resource"),
params[i].key);
goto cleanup;
}
}
if (!path || !lockspace) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Missing path or lockspace for lease resource"));
goto cleanup;
}
newLockspace = g_strdup_printf("%s/%s", path, lockspace);
newName = g_strdup(name);
} break;
default:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unknown lock manager object type %d"),
type);
goto cleanup;
}
if (VIR_EXPAND_N(priv->resources, priv->nresources, 1) < 0)
goto cleanup;
priv->resources[priv->nresources-1].lockspace = g_steal_pointer(&newLockspace);
priv->resources[priv->nresources-1].name = g_steal_pointer(&newName);
if (flags & VIR_LOCK_MANAGER_RESOURCE_SHARED)
priv->resources[priv->nresources-1].flags |=
VIR_LOCK_SPACE_PROTOCOL_ACQUIRE_RESOURCE_SHARED;
if (autoCreate)
priv->resources[priv->nresources-1].flags |=
VIR_LOCK_SPACE_PROTOCOL_ACQUIRE_RESOURCE_AUTOCREATE;
ret = 0;
cleanup:
VIR_FREE(newLockspace);
VIR_FREE(newName);
return ret;
}
static int virLockManagerLockDaemonAcquire(virLockManagerPtr lock,
const char *state G_GNUC_UNUSED,
unsigned int flags,
virDomainLockFailureAction action G_GNUC_UNUSED,
int *fd)
{
virNetClientPtr client = NULL;
virNetClientProgramPtr program = NULL;
int counter = 0;
int rv = -1;
virLockManagerLockDaemonPrivatePtr priv = lock->privateData;
virCheckFlags(VIR_LOCK_MANAGER_ACQUIRE_REGISTER_ONLY |
VIR_LOCK_MANAGER_ACQUIRE_RESTRICT, -1);
if (priv->nresources == 0 &&
priv->hasRWDisks &&
driver->requireLeaseForDisks) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("Read/write, exclusive access, disks were present, but no leases specified"));
return -1;
}
if (!(client = virLockManagerLockDaemonConnect(lock, &program, &counter)))
goto cleanup;
if (fd &&
(*fd = virNetClientDupFD(client, false)) < 0)
goto cleanup;
if (!(flags & VIR_LOCK_MANAGER_ACQUIRE_REGISTER_ONLY)) {
size_t i;
for (i = 0; i < priv->nresources; i++) {
virLockSpaceProtocolAcquireResourceArgs args;
memset(&args, 0, sizeof(args));
args.path = priv->resources[i].lockspace;
args.name = priv->resources[i].name;
args.flags = priv->resources[i].flags;
if (virNetClientProgramCall(program,
client,
counter++,
VIR_LOCK_SPACE_PROTOCOL_PROC_ACQUIRE_RESOURCE,
0, NULL, NULL, NULL,
(xdrproc_t)xdr_virLockSpaceProtocolAcquireResourceArgs, &args,
(xdrproc_t)xdr_void, NULL) < 0)
goto cleanup;
}
}
if ((flags & VIR_LOCK_MANAGER_ACQUIRE_RESTRICT) &&
virLockManagerLockDaemonConnectionRestrict(lock, client, program, &counter) < 0)
goto cleanup;
rv = 0;
cleanup:
if (rv != 0 && fd)
VIR_FORCE_CLOSE(*fd);
virNetClientClose(client);
virObjectUnref(client);
virObjectUnref(program);
return rv;
}
static int virLockManagerLockDaemonRelease(virLockManagerPtr lock,
char **state,
unsigned int flags)
{
virNetClientPtr client = NULL;
virNetClientProgramPtr program = NULL;
int counter = 0;
int rv = -1;
size_t i;
virLockManagerLockDaemonPrivatePtr priv = lock->privateData;
virCheckFlags(0, -1);
if (state)
*state = NULL;
if (!(client = virLockManagerLockDaemonConnect(lock, &program, &counter)))
goto cleanup;
for (i = 0; i < priv->nresources; i++) {
virLockSpaceProtocolReleaseResourceArgs args;
memset(&args, 0, sizeof(args));
if (priv->resources[i].lockspace)
args.path = priv->resources[i].lockspace;
args.name = priv->resources[i].name;
args.flags = priv->resources[i].flags;
args.flags &=
~(VIR_LOCK_SPACE_PROTOCOL_ACQUIRE_RESOURCE_SHARED |
VIR_LOCK_SPACE_PROTOCOL_ACQUIRE_RESOURCE_AUTOCREATE);
if (virNetClientProgramCall(program,
client,
counter++,
VIR_LOCK_SPACE_PROTOCOL_PROC_RELEASE_RESOURCE,
0, NULL, NULL, NULL,
(xdrproc_t)xdr_virLockSpaceProtocolReleaseResourceArgs, &args,
(xdrproc_t)xdr_void, NULL) < 0)
goto cleanup;
}
rv = 0;
cleanup:
virNetClientClose(client);
virObjectUnref(client);
virObjectUnref(program);
return rv;
}
static int virLockManagerLockDaemonInquire(virLockManagerPtr lock G_GNUC_UNUSED,
char **state,
unsigned int flags)
{
virCheckFlags(0, -1);
if (state)
*state = NULL;
return 0;
}
virLockDriver virLockDriverImpl =
{
.version = VIR_LOCK_MANAGER_VERSION,
.flags = 0,
.drvInit = virLockManagerLockDaemonInit,
.drvDeinit = virLockManagerLockDaemonDeinit,
.drvNew = virLockManagerLockDaemonNew,
.drvFree = virLockManagerLockDaemonFree,
.drvAddResource = virLockManagerLockDaemonAddResource,
.drvAcquire = virLockManagerLockDaemonAcquire,
.drvRelease = virLockManagerLockDaemonRelease,
.drvInquire = virLockManagerLockDaemonInquire,
};