/* * lock_driver_lockd.c: A lock driver which locks nothing * * Copyright (C) 2010-2011 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 "virlog.h" #include "viruuid.h" #include "virfile.h" #include "virerror.h" #include "rpc/virnetclient.h" #include "lock_protocol.h" #include "configmake.h" #include "sha256.h" #include "virstring.h" #define VIR_FROM_THIS VIR_FROM_LOCKING #define virLockError(code, ...) \ virReportErrorHelper(VIR_FROM_THIS, code, __FILE__, \ __FUNCTION__, __LINE__, __VA_ARGS__) 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 = NULL; #define VIRTLOCKD_PATH SBINDIR "/virtlockd" static const char * virLockManagerLockDaemonFindDaemon(void) { const char *customDaemon = getenv("VIRTLOCKD_PATH"); if (customDaemon) return customDaemon; if (virFileIsExecutable(VIRTLOCKD_PATH)) return VIRTLOCKD_PATH; return NULL; } static int virLockManagerLockDaemonLoadConfig(const char *configFile) { virConfPtr conf; virConfValuePtr p; 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; #define CHECK_TYPE(name,typ) if (p && p->type != (typ)) { \ virReportError(VIR_ERR_INTERNAL_ERROR, \ "%s: %s: expected type " #typ, \ configFile, (name)); \ virConfFree(conf); \ return -1; \ } p = virConfGetValue(conf, "auto_disk_leases"); CHECK_TYPE("auto_disk_leases", VIR_CONF_LONG); if (p) driver->autoDiskLease = p->l; p = virConfGetValue(conf, "file_lockspace_dir"); CHECK_TYPE("file_lockspace_dir", VIR_CONF_STRING); if (p && p->str) { VIR_FREE(driver->fileLockSpaceDir); if (!(driver->fileLockSpaceDir = strdup(p->str))) { virReportOOMError(); virConfFree(conf); return -1; } } p = virConfGetValue(conf, "lvm_lockspace_dir"); CHECK_TYPE("lvm_lockspace_dir", VIR_CONF_STRING); if (p && p->str) { VIR_FREE(driver->lvmLockSpaceDir); if (!(driver->lvmLockSpaceDir = strdup(p->str))) { virReportOOMError(); virConfFree(conf); return -1; } } p = virConfGetValue(conf, "scsi_lockspace_dir"); CHECK_TYPE("scsi_lockspace_dir", VIR_CONF_STRING); if (p && p->str) { VIR_FREE(driver->scsiLockSpaceDir); if (!(driver->scsiLockSpaceDir = strdup(p->str))) { virReportOOMError(); virConfFree(conf); return -1; } } p = virConfGetValue(conf, "require_lease_for_disks"); CHECK_TYPE("require_lease_for_disks", VIR_CONF_LONG); if (p) driver->requireLeaseForDisks = p->l; else driver->requireLeaseForDisks = !driver->autoDiskLease; virConfFree(conf); return 0; } static char *virLockManagerLockDaemonPath(bool privileged) { char *path; if (privileged) { if (!(path = strdup(LOCALSTATEDIR "/run/libvirt/virtlockd-sock"))) { virReportOOMError(); return NULL; } } else { char *rundir = NULL; if (!(rundir = virGetUserRuntimeDirectory())) return NULL; if (virAsprintf(&path, "%s/virtlockd-sock", rundir) < 0) { VIR_FREE(rundir); virReportOOMError(); return NULL; } } return path; } static int virLockManagerLockDaemonConnectionRegister(virLockManagerPtr lock, virNetClientPtr client, virNetClientProgramPtr program, int *counter) { virLockManagerLockDaemonPrivatePtr priv = lock->privateData; virLockSpaceProtocolRegisterArgs args; int rv = -1; 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) goto cleanup; rv = 0; cleanup: return rv; } static int virLockManagerLockDaemonConnectionRestrict(virLockManagerPtr lock ATTRIBUTE_UNUSED, virNetClientPtr client, virNetClientProgramPtr program, int *counter) { virLockSpaceProtocolRestrictArgs args; int rv = -1; 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) goto cleanup; rv = 0; cleanup: return rv; } static virNetClientPtr virLockManagerLockDaemonConnectionNew(bool privileged, virNetClientProgramPtr *prog) { virNetClientPtr client = NULL; char *lockdpath; const char *daemonPath = NULL; *prog = NULL; if (!(lockdpath = virLockManagerLockDaemonPath(privileged))) goto error; if (!privileged) daemonPath = virLockManagerLockDaemonFindDaemon(); 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(lockdpath); return client; error: 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(getuid() == 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(getuid() == 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) { virErrorPtr err = virGetLastError(); if (err && err->code == 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=%x", version, NULLSTR(configFile), flags); virCheckFlags(0, -1); if (driver) return 0; if (VIR_ALLOC(driver) < 0) { virReportOOMError(); return -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->fileLockSpaceDir); VIR_FREE(driver); return 0; } static void virLockManagerLockDaemonFree(virLockManagerPtr lock) { virLockManagerLockDaemonPrivatePtr priv = lock->privateData; size_t i; if (!priv) return; lock->privateData = NULL; 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 int virLockManagerLockDaemonNew(virLockManagerPtr lock, unsigned int type, size_t nparams, virLockManagerParamPtr params, unsigned int flags) { virLockManagerLockDaemonPrivatePtr priv; size_t i; virCheckFlags(VIR_LOCK_MANAGER_USES_STATE, -1); if (VIR_ALLOC(priv) < 0) { virReportOOMError(); return -1; } lock->privateData = priv; 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")) { if (!(priv->name = strdup(params[i].value.str))) { virReportOOMError(); return -1; } } else if (STREQ(params[i].key, "id")) { priv->id = params[i].value.i; } else if (STREQ(params[i].key, "pid")) { priv->pid = params[i].value.i; } else { virReportError(VIR_ERR_INTERNAL_ERROR, _("Unexpected parameter %s for object"), params[i].key); } } if (priv->id == 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Missing ID parameter for domain object")); return -1; } if (priv->pid == 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Missing PID parameter for domain object")); return -1; } if (!priv->name) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Missing name parameter for domain object")); return -1; } if (!virUUIDIsValid(priv->uuid)) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Missing UUID parameter for domain object")); return -1; } break; default: virReportError(VIR_ERR_INTERNAL_ERROR, _("Unknown lock manager object type %d"), type); return -1; } return 0; } static const char hex[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; static char *virLockManagerLockDaemonDiskLeaseName(const char *path) { unsigned char buf[SHA256_DIGEST_SIZE]; char *ret; int i; if (!(sha256_buffer(path, strlen(path), buf))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Unable to compute sha256 checksum")); return NULL; } if (VIR_ALLOC_N(ret, (SHA256_DIGEST_SIZE * 2) + 1) < 0) { virReportOOMError(); return NULL; } for (i = 0 ; i < SHA256_DIGEST_SIZE ; i++) { ret[i*2] = hex[(buf[i] >> 4) & 0xf]; ret[(i*2)+1] = hex[buf[i] & 0xf]; } ret[(SHA256_DIGEST_SIZE * 2) + 1] = '\0'; return ret; } 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; 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")); return -1; } 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 (virStorageFileGetLVMKey(name, &newName) < 0) goto error; if (newName) { VIR_DEBUG("Got an LVM UUID %s for %s", newName, name); if (!(newLockspace = strdup(driver->lvmLockSpaceDir))) goto no_memory; 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) < 0) goto error; if (newName) { VIR_DEBUG("Got an SCSI ID %s for %s", newName, name); if (!(newLockspace = strdup(driver->scsiLockSpaceDir))) goto no_memory; autoCreate = true; break; } virResetLastError(); /* Fallback to generic non-block code */ } if (driver->fileLockSpaceDir) { if (!(newLockspace = strdup(driver->fileLockSpaceDir))) goto no_memory; if (!(newName = virLockManagerLockDaemonDiskLeaseName(name))) goto no_memory; autoCreate = true; VIR_DEBUG("Using indirect lease %s for %s", newName, name); } else { if (!(newLockspace = strdup(""))) goto no_memory; if (!(newName = strdup(name))) goto no_memory; 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")); return -1; } } 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); return -1; } } if (!path || !lockspace) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Missing path or lockspace for lease resource")); return -1; } if (virAsprintf(&newLockspace, "%s/%s", path, lockspace) < 0) { virReportOOMError(); return -1; } if (!(newName = strdup(name))) goto no_memory; } break; default: virReportError(VIR_ERR_INTERNAL_ERROR, _("Unknown lock manager object type %d"), type); return -1; } if (VIR_EXPAND_N(priv->resources, priv->nresources, 1) < 0) goto no_memory; priv->resources[priv->nresources-1].lockspace = newLockspace; priv->resources[priv->nresources-1].name = 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; return 0; no_memory: virReportOOMError(); error: VIR_FREE(newLockspace); VIR_FREE(newName); return -1; } static int virLockManagerLockDaemonAcquire(virLockManagerPtr lock, const char *state ATTRIBUTE_UNUSED, unsigned int flags, virDomainLockFailureAction action ATTRIBUTE_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)); if (priv->resources[i].lockspace) 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 ATTRIBUTE_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, };