dlm is implemented by linux kernel, provides userspace API by 'libdlm' to lock/unlock resource, cooperated with cluster communication software, such as corosync, could satisfy the demands of libvirt lock manager. Signed-off-by: river <lfu@xxxxxxxx> --- configure.ac | 6 + m4/virt-cpg.m4 | 37 ++ m4/virt-dlm.m4 | 36 ++ src/Makefile.am | 15 + src/locking/lock_driver_dlm.c | 1056 +++++++++++++++++++++++++++++++++++++++++ src/util/virlist.h | 110 +++++ 6 files changed, 1260 insertions(+) create mode 100644 m4/virt-cpg.m4 create mode 100644 m4/virt-dlm.m4 create mode 100644 src/locking/lock_driver_dlm.c create mode 100644 src/util/virlist.h diff --git a/configure.ac b/configure.ac index 4cccf7f4d..4ad90470d 100644 --- a/configure.ac +++ b/configure.ac @@ -265,6 +265,8 @@ LIBVIRT_ARG_PM_UTILS LIBVIRT_ARG_POLKIT LIBVIRT_ARG_READLINE LIBVIRT_ARG_SANLOCK +LIBVIRT_ARG_CPG +LIBVIRT_ARG_DLM LIBVIRT_ARG_SASL LIBVIRT_ARG_SELINUX LIBVIRT_ARG_SSH2 @@ -307,6 +309,8 @@ LIBVIRT_CHECK_POLKIT LIBVIRT_CHECK_PTHREAD LIBVIRT_CHECK_READLINE LIBVIRT_CHECK_SANLOCK +LIBVIRT_CHECK_CPG +LIBVIRT_CHECK_DLM LIBVIRT_CHECK_SASL LIBVIRT_CHECK_SELINUX LIBVIRT_CHECK_SSH2 @@ -1005,6 +1009,8 @@ LIBVIRT_RESULT_POLKIT LIBVIRT_RESULT_RBD LIBVIRT_RESULT_READLINE LIBVIRT_RESULT_SANLOCK +LIBVIRT_RESULT_CPG +LIBVIRT_RESULT_DLM LIBVIRT_RESULT_SASL LIBVIRT_RESULT_SELINUX LIBVIRT_RESULT_SSH2 diff --git a/m4/virt-cpg.m4 b/m4/virt-cpg.m4 new file mode 100644 index 000000000..27acda665 --- /dev/null +++ b/m4/virt-cpg.m4 @@ -0,0 +1,37 @@ +dnl The libcpg.so library +dnl +dnl Copyright (C) 2018 SUSE LINUX Products, Beijing, China. +dnl +dnl This library is free software; you can redistribute it and/or +dnl modify it under the terms of the GNU Lesser General Public +dnl License as published by the Free Software Foundation; either +dnl version 2.1 of the License, or (at your option) any later version. +dnl +dnl This library is distributed in the hope that it will be useful, +dnl but WITHOUT ANY WARRANTY; without even the implied warranty of +dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +dnl Lesser General Public License for more details. +dnl +dnl You should have received a copy of the GNU Lesser General Public +dnl License along with this library. If not, see +dnl <http://www.gnu.org/licenses/>. +dnl + +AC_DEFUN([LIBVIRT_ARG_CPG],[ + LIBVIRT_ARG_WITH_FEATURE([CPG], [cluster engine CPG library], [check]) +]) + +AC_DEFUN([LIBVIRT_CHECK_CPG],[ + dnl in some distribution, Version is `UNKNOW` in libcpg.pc + if test "x$with_cpg" != "xno"; then + PKG_CHECK_MODULES([CPG], [libcpg], [ + with_cpg=yes + ],[ + with_cpg=no + ]) + fi +]) + +AC_DEFUN([LIBVIRT_RESULT_CPG],[ + LIBVIRT_RESULT_LIB([CPG]) +]) diff --git a/m4/virt-dlm.m4 b/m4/virt-dlm.m4 new file mode 100644 index 000000000..dc5f5f152 --- /dev/null +++ b/m4/virt-dlm.m4 @@ -0,0 +1,36 @@ +dnl The libdlm.so library +dnl +dnl Copyright (C) 2018 SUSE LINUX Products, Beijing, China. +dnl +dnl This library is free software; you can redistribute it and/or +dnl modify it under the terms of the GNU Lesser General Public +dnl License as published by the Free Software Foundation; either +dnl version 2.1 of the License, or (at your option) any later version. +dnl +dnl This library is distributed in the hope that it will be useful, +dnl but WITHOUT ANY WARRANTY; without even the implied warranty of +dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +dnl Lesser General Public License for more details. +dnl +dnl You should have received a copy of the GNU Lesser General Public +dnl License along with this library. If not, see +dnl <http://www.gnu.org/licenses/>. +dnl + +AC_DEFUN([LIBVIRT_ARG_DLM],[ + LIBVIRT_ARG_WITH_FEATURE([DLM], [Distributed Lock Manager library], [check]) +]) + +AC_DEFUN([LIBVIRT_CHECK_DLM],[ + AC_REQUIRE([LIBVIRT_CHECK_CPG]) + LIBVIRT_CHECK_PKG([DLM], [libdlm], [4.0.0]) + + if test "x$with_dlm" == "xyes" && test "x$with_cpg" != "xyes"; then + AC_MSG_ERROR([You must install libcpg to build dlm lock]) + fi +]) + +AC_DEFUN([LIBVIRT_RESULT_DLM],[ + AC_REQUIRE([LIBVIRT_RESULT_CPG]) + LIBVIRT_RESULT_LIB([DLM]) +]) diff --git a/src/Makefile.am b/src/Makefile.am index 79adc9ba5..8742921fa 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -305,6 +305,10 @@ DRIVER_SOURCES = \ logging/log_manager.c logging/log_manager.h \ $(NULL) +LOCK_DRIVER_DLM_SOURCES = \ + locking/lock_driver_dlm.c \ + $(NULL) + LOCK_DRIVER_SANLOCK_SOURCES = \ locking/lock_driver_sanlock.c @@ -2879,6 +2883,17 @@ virtlogd.socket: logging/virtlogd.socket.in $(top_builddir)/config.status < $< > $@-t && \ mv $@-t $@ +if WITH_DLM +lockdriver_LTLIBRARIES += dlm.la +dlm_la_SOURCES = $(LOCK_DRIVER_DLM_SOURCES) +dlm_la_CFLAGS = -I$(srcdir)/conf $(AM_CFLAGS) +dlm_la_LDFLAGS = -module -avoid-version $(AM_LDFLAGS) +dlm_la_LIBADD = ../gnulib/lib/libgnu.la \ + $(CPG_LIBS) \ + $(DLM_LIBS) +else ! WITH_DLM +EXTRA_DIST += $(LOCK_DRIVER_DLM_SOURCES) +endif if WITH_SANLOCK lockdriver_LTLIBRARIES += sanlock.la diff --git a/src/locking/lock_driver_dlm.c b/src/locking/lock_driver_dlm.c new file mode 100644 index 000000000..e197c0bdf --- /dev/null +++ b/src/locking/lock_driver_dlm.c @@ -0,0 +1,1056 @@ +/* + * lock_driver_dlm.c: a lock driver for dlm + * + * Copyright (C) 2018 SUSE LINUX Products, Beijing, China. + * + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <stdint.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> + +#include <corosync/cpg.h> +#include <libdlm.h> + +#include "lock_driver.h" +#include "viralloc.h" +#include "virconf.h" +#include "vircrypto.h" +#include "virerror.h" +#include "virfile.h" +#include "virlist.h" +#include "virlog.h" +#include "virstring.h" +#include "virthread.h" +#include "viruuid.h" + +#define VIR_FROM_THIS VIR_FROM_LOCKING + +#define DLM_LOCKSPACE_MODE 0600 +#define DLM_LOCKSPACE_NAME "libvirt" + +#define LOCK_RECORD_FILE_MODE 0644 +#define LOCK_RECORD_FILE_PATH "/tmp/libvirtd-dlm-file" + +#define PRMODE "PRMODE" +#define EXMODE "EXMODE" + +#define STATUS "STATUS" +#define RESOURCE_NAME "RESOURCE_NAME" +#define LOCK_ID "LOCK_ID" +#define LOCK_MODE "LOCK_MODE" +#define VM_PID "VM_PID" + +#define BUFFERLEN 128 + +/* This will be set after dlm_controld is started. */ +#define DLM_CLUSTER_NAME_PATH "/sys/kernel/config/dlm/cluster/cluster_name" + +VIR_LOG_INIT("locking.lock_driver_dlm"); + +typedef struct _virLockInformation virLockInformation; +typedef virLockInformation *virLockInformationPtr; + +typedef struct _virLockManagerDlmResource virLockManagerDlmResource; +typedef virLockManagerDlmResource *virLockManagerDlmResourcePtr; + +typedef struct _virLockManagerDlmPrivate virLockManagerDlmPrivate; +typedef virLockManagerDlmPrivate *virLockManagerDlmPrivatePtr; + +typedef struct _virLockManagerDlmDriver virLockManagerDlmDriver; +typedef virLockManagerDlmDriver *virLockManagerDlmDriverPtr; + +typedef struct _virListWait virListWait; +typedef virListWait *virListWaitPtr; + +struct _virLockInformation { + virListHead entry; + char *name; + uint32_t mode; + uint32_t lkid; + pid_t vm_pid; +}; + +struct _virLockManagerDlmResource { + char *name; + uint32_t mode; +}; + +struct _virLockManagerDlmPrivate { + unsigned char vm_uuid[VIR_UUID_BUFLEN]; + char *vm_name; + pid_t vm_pid; + int vm_id; + + size_t nresources; + virLockManagerDlmResourcePtr resources; + + bool hasRWDisks; +}; + +struct _virLockManagerDlmDriver { + bool autoDiskLease; + bool requireLeaseForDisks; + + bool purgeLockspace; + char *lockspaceName; + char *lockRecordFilePath; +}; + +struct _virListWait { + virMutex listMutex; + virMutex fileMutex; + virListHead list; +}; + +static virLockManagerDlmDriverPtr driver; +static dlm_lshandle_t lockspace; +static virListWait lockListWait; + +static int virLockManagerDlmLoadConfig(const char *configFile) +{ + virConfPtr conf = NULL; + int ret = -1; + + 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) + goto cleanup; + + driver->requireLeaseForDisks = !driver->autoDiskLease; + if (virConfGetValueBool(conf, "require_lease_for_disks", &driver->requireLeaseForDisks) < 0) + goto cleanup; + + if (virConfGetValueBool(conf, "purge_lockspace", &driver->purgeLockspace) < 0) + goto cleanup; + + if (virConfGetValueString(conf, "lockspace_name", &driver->lockspaceName) < 0) + goto cleanup; + + if (virConfGetValueString(conf, "lock_record_file_path", &driver->lockRecordFilePath) < 0) + goto cleanup; + + ret = 0; + + cleanup: + virConfFree(conf); + return ret; +} + +static int virLockManagerDlmToModeUint(const char *token) +{ + if (STREQ(token, PRMODE)) + return LKM_PRMODE; + if (STREQ(token, EXMODE)) + return LKM_EXMODE; + + return 0; +} + +static const char *virLockManagerDlmToModeText(const uint32_t mode) +{ + switch (mode) { + case LKM_PRMODE: + return PRMODE; + case LKM_EXMODE: + return EXMODE; + default: + return NULL; + } +} + +static virLockInformationPtr virLockManagerDlmRecordLock(const char *name, + const uint32_t mode, + const uint32_t lkid, + const pid_t vm_pid) +{ + virLockInformationPtr lock = NULL; + + if (VIR_ALLOC(lock) < 0) + goto error; + + if (VIR_STRDUP(lock->name, name) < 0) + goto error; + + lock->mode = mode; + lock->lkid = lkid; + lock->vm_pid = vm_pid; + + virMutexLock(&(lockListWait.listMutex)); + virListAddTail(&lock->entry, &(lockListWait.list)); + virMutexUnlock(&(lockListWait.listMutex)); + + VIR_DEBUG("record lock sucessfully, lockName=%s lockMode=%s lockId=%d", + NULLSTR(name), NULLSTR(virLockManagerDlmToModeText(mode)), lkid); + + return lock; + + error: + if (lock) + VIR_FREE(lock->name); + VIR_FREE(lock); + return NULL; +} + +static void virLockManagerDlmWriteLock(virLockInformationPtr lock, int fd, bool status) +{ + char buffer[BUFFERLEN] = {0}; + off_t offset = 0, rv = 0; + + if (!lock) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("lock is NULL")); + return; + } + + /* + * STATUS RESOURCE_NAME LOCK_MODE VM_PID\n + * 6 64 9 10 + * 93 = 6 + 1 + 64 + 1 + 9 + 1 + 10 + 1 + */ + offset = 93 * lock->lkid; + rv = lseek(fd, offset, SEEK_SET); + if (rv < 0) { + virReportSystemError(errno, + _("unable to lseek fd '%d'"), + fd); + return; + } + + snprintf(buffer, sizeof(buffer), "%6d %64s %9s %10jd\n", \ + status, lock->name, + NULLSTR(virLockManagerDlmToModeText(lock->mode)), + (intmax_t)lock->vm_pid); + + if (safewrite(fd, buffer, strlen(buffer)) != strlen(buffer)) { + virReportSystemError(errno, + _("unable to write lock information '%s' to file '%s'"), + buffer, NULLSTR(driver->lockRecordFilePath)); + return; + } + + VIR_DEBUG("write '%s' to fd=%d", buffer, fd); + + fdatasync(fd); + + return; +} + +static void virLockManagerDlmAdoptLock(char *raw) { + char *str = NULL, *subtoken = NULL, *saveptr = NULL, *endptr = NULL; + int i = 0, status = 0; + char *name = NULL; + uint32_t mode = 0; + pid_t vm_pid = 0; + struct dlm_lksb lksb = {0}; + + /* every line is the following format: + * STATUS RESOURCE_NAME LOCK_MODE VM_PID + */ + for (i = 0, str = raw, status = 0; ; str = NULL, i++) { + subtoken = strtok_r(str, " \n", &saveptr); + if (subtoken == NULL) + break; + + switch(i) { + case 0: + if (virStrToLong_i(subtoken, &endptr, 10, &status) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("cannot extract lock status '%s'"), subtoken); + goto cleanup; + } + break; + case 1: + if (VIR_STRDUP(name, subtoken) != 1) + goto cleanup; + break; + case 2: + mode = virLockManagerDlmToModeUint(subtoken); + if (!mode) + goto cleanup; + break; + case 3: + if ((virStrToLong_i(subtoken, &endptr, 10, &vm_pid) < 0) || !vm_pid) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("cannot extract lock vm_pid '%s'"), subtoken); + goto cleanup; + } + break; + default: + goto cleanup; + break; + } + + if (status != 1) + goto cleanup; + } + + if (i != 4) + goto cleanup; + + /* copy from `lm_adopt_dlm` in daemons/lvmlockd/lvmlockd-dlm.c of lvm2: + * dlm returns 0 for success, -EAGAIN if an orphan is + * found with another mode, and -ENOENT if no orphan. + * + * cast/bast/param are (void *)1 because the kernel + * returns errors if some are null. + */ + + status = dlm_ls_lockx(lockspace, mode, &lksb, LKF_PERSISTENT|LKF_ORPHAN, + name, strlen(name), 0, + (void *)1, (void *)1, (void *)1, + NULL, NULL); + if (status) { + virReportSystemError(errno, + _("unable to adopt lock, rv=%d lockName=%s lockMode=%s"), + status, name, NULLSTR(virLockManagerDlmToModeText(mode))); + goto cleanup; + } + + if (!virLockManagerDlmRecordLock(name, mode, lksb.sb_lkid, vm_pid)) { + virReportSystemError(errno, + _("unable to record lock information, " + "lockName=%s lockMode=%s lockId=%d vm_pid=%jd"), + NULLSTR(name), NULLSTR(virLockManagerDlmToModeText(mode)), + lksb.sb_lkid, (intmax_t)vm_pid); + } + + + cleanup: + if (name) + VIR_FREE(name); + + return; +} + +static int virLockManagerDlmPrepareLockList(const char *lockRecordFilePath) +{ + FILE *fp = NULL; + int line = 0; + size_t n = 0; + ssize_t count = 0; + char *buffer = NULL; + + fp = fopen(lockRecordFilePath, "r"); + if (!fp) { + virReportSystemError(errno, + _("unable to open '%s'"), lockRecordFilePath); + return -1; + } + + /* lock information is from the second line */ + for (line = 0; !feof(fp); line++) { + count = getline(&buffer, &n, fp); + if (count <= 0) + break; + + switch (line) { + case 0: + break; + default: + virLockManagerDlmAdoptLock(buffer); + break; + } + } + + VIR_FORCE_FCLOSE(fp); + VIR_FREE(buffer); + + return 0; +} + +static int virLockManagerDlmGetLocalNodeId(uint32_t *nodeId) +{ + cpg_handle_t handle = 0; + int rv = -1; + + if (cpg_model_initialize(&handle, CPG_MODEL_V1, NULL, NULL) != CS_OK) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("unable to create a new connection to the CPG service")); + return -1; + } + + if( cpg_local_get(handle, nodeId) != CS_OK) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("unable to get the local node id by the CPG service")); + goto cleanup; + } + + VIR_DEBUG("the local nodeid=%u", *nodeId); + + rv = 0; + + cleanup: + if (cpg_finalize(handle) != CS_OK) + VIR_WARN("unable to finalize the CPG service"); + + return rv; +} + +static int virLockManagerDlmDumpLockList(const char *lockRecordFilePath) +{ + virLockInformationPtr theLock = NULL; + char buffer[BUFFERLEN] = {0}; + int fd = -1, rv = -1; + + /* not need mutex because of only one instance would be initialized */ + fd = open(lockRecordFilePath, O_WRONLY|O_CREAT|O_TRUNC, LOCK_RECORD_FILE_MODE); + if (fd < 0) { + virReportSystemError(errno, + _("unable to open '%s'"), + lockRecordFilePath); + return -1; + } + + snprintf(buffer, sizeof(buffer), "%6s %64s %9s %10s\n", \ + STATUS, RESOURCE_NAME, LOCK_MODE, VM_PID); + if (safewrite(fd, buffer, strlen(buffer)) != strlen(buffer)) { + virReportSystemError(errno, + _("unable to write '%s' to '%s'"), + buffer, lockRecordFilePath); + goto cleanup; + } + + virListForEachEntry(theLock, &(lockListWait.list), entry) { + virLockManagerDlmWriteLock(theLock, fd, 1); + } + + if (VIR_CLOSE(fd) < 0) { + virReportSystemError(errno, + _("unable to close file '%s'"), + lockRecordFilePath); + goto cleanup; + } + + rv = 0; + + cleanup: + if (rv) + VIR_FORCE_CLOSE(fd); + return rv; +} + +static int virLockManagerDlmSetupLockRecordFile(const char *lockRecordFilePath, + const bool newLockspace, + const bool purgeLockspace) +{ + uint32_t nodeId = 0; + + /* there maybe some orphan locks recorded in the lock record file which + * should be adopted if lockspace is opened instead of created, we adopt + * them then add them in the list. + */ + if (!newLockspace && + virLockManagerDlmPrepareLockList(lockRecordFilePath)) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unable to adopt locks from '%s'"), + NULLSTR(lockRecordFilePath)); + return -1; + } + + /* purgeLockspace flag means purging orphan locks belong to any process + * in this lockspace. + */ + if (purgeLockspace && !virLockManagerDlmGetLocalNodeId(&nodeId)) { + if (dlm_ls_purge(lockspace, nodeId, 0)) { + VIR_WARN("node=%u purge DLM locks failed in lockspace=%s", + nodeId, NULLSTR(driver->lockspaceName)); + } + else + VIR_DEBUG("node=%u purge DLM locks success in lockspace=%s", + nodeId, NULLSTR(driver->lockspaceName)); + } + + /* initialize the lock record file */ + if (virLockManagerDlmDumpLockList(lockRecordFilePath)) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unable to initialize the lock record file '%s'"), + lockRecordFilePath); + return -1; + } + + return 0; +} + +static int virLockManagerDlmSetup(void) +{ + bool newLockspace = false; + + virListHeadInit(&(lockListWait.list)); + if ((virMutexInit(&(lockListWait.listMutex)) < 0) || + (virMutexInit(&(lockListWait.fileMutex)) < 0)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("unable to initialize mutex")); + return -1; + } + + + /* check whether dlm is running or not */ + if (access(DLM_CLUSTER_NAME_PATH, F_OK)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("check dlm_controld, ensure it has setuped")); + return -1; + } + + /* open lockspace, create it if it doesn't exist */ + lockspace = dlm_open_lockspace(driver->lockspaceName); + if (!lockspace) { + lockspace = dlm_create_lockspace(driver->lockspaceName, DLM_LOCKSPACE_MODE); + if (!lockspace) { + virReportSystemError(errno, "%s", + _("unable to open and create DLM lockspace")); + return -1; + } + newLockspace = true; + } + + /* create thread to receive notification from kernel */ + if (dlm_ls_pthread_init(lockspace)) { + if (errno != EEXIST) { + virReportSystemError(errno, "%s", + _("unable to initialize lockspace")); + return -1; + } + } + + /* we need file to record lock information used by rebooted libvirtd */ + if (virLockManagerDlmSetupLockRecordFile(driver->lockRecordFilePath, + newLockspace, + driver->purgeLockspace)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("unable to initialize DLM lock file")); + return -1; + } + + return 0; +} + +static int virLockManagerDlmDeinit(void); + +static int virLockManagerDlmInit(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; + + if (geteuid() != 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("dlm lock requires root privileges")); + return -1; + } + + if (VIR_ALLOC(driver) < 0) + return -1; + + driver->autoDiskLease = true; + driver->requireLeaseForDisks = !driver->autoDiskLease; + driver->purgeLockspace = true; + + if (virAsprintf(&driver->lockspaceName, + "%s", DLM_LOCKSPACE_NAME) < 0) + goto error; + + if (virAsprintf(&driver->lockRecordFilePath, + "%s", LOCK_RECORD_FILE_PATH) < 0) + goto error; + + if (virLockManagerDlmLoadConfig(configFile) < 0) + goto error; + + if (virLockManagerDlmSetup() < 0) + goto error; + + return 0; + + error: + virLockManagerDlmDeinit(); + return -1; +} + +static int virLockManagerDlmDeinit(void) +{ + virLockInformationPtr theLock = NULL; + + if (!driver) + return 0; + + if(lockspace) + dlm_close_lockspace(lockspace); + + /* not care about whether adopting lock or not, + * just release those to prevent memory leak + */ + virListForEachEntry(theLock, &(lockListWait.list), entry) { + virListDelete(&(theLock->entry)); + VIR_FREE(theLock->name); + VIR_FREE(theLock); + } + + VIR_FREE(driver->lockspaceName); + VIR_FREE(driver->lockRecordFilePath); + VIR_FREE(driver); + + return 0; +} + +static int virLockManagerDlmNew(virLockManagerPtr lock, + unsigned int type, + size_t nparams, + virLockManagerParamPtr params, + unsigned int flags) +{ + virLockManagerDlmPrivatePtr priv = NULL; + size_t i; + + virCheckFlags(VIR_LOCK_MANAGER_NEW_STARTED, -1); + + if (!driver) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("dlm plugin is not initialized")); + return -1; + } + + if (type != VIR_LOCK_MANAGER_OBJECT_TYPE_DOMAIN) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unsupported object type %d"), type); + return -1; + } + + if (VIR_ALLOC(priv) < 0) + return -1; + + for (i = 0; i< nparams; i++) { + if (STREQ(params[i].key, "uuid")) { + memcpy(priv->vm_uuid, params[i].value.uuid, VIR_UUID_BUFLEN); + } else if (STREQ(params[i].key, "name")) { + if (VIR_STRDUP(priv->vm_name, params[i].value.str) < 0) + return -1; + } else if (STREQ(params[i].key, "id")) { + priv->vm_id = params[i].value.ui; + } else if (STREQ(params[i].key, "pid")) { + priv->vm_pid = params[i].value.iv; + } else if (STREQ(params[i].key, "uri")) { + /* there would be a warning in some case according to the history patch, + * so ignored + */ + } else { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unexpected parameter %s for object"), + params[i].key); + } + } + + /* check the following to prevent some unexpexted state in some case */ + if (priv->vm_pid == 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing PID parameter for domain object")); + return -1; + } + if (!priv->vm_name) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing name parameter for domain object")); + return -1; + } + + if (priv->vm_id == 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing ID parameter for domain object")); + return -1; + } + if (!virUUIDIsValid(priv->vm_uuid)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing UUID parameter for domain object")); + return -1; + } + + lock->privateData = priv; + + return 0; +} + +static void virLockManagerDlmFree(virLockManagerPtr lock) +{ + virLockManagerDlmPrivatePtr priv = lock->privateData; + size_t i; + + if (!priv) + return; + + for (i = 0; i < priv->nresources; i++) + VIR_FREE(priv->resources[i].name); + + VIR_FREE(priv->resources); + VIR_FREE(priv->vm_name); + VIR_FREE(priv); + lock->privateData = NULL; + + return; +} + +static int virLockManagerDlmAddResource(virLockManagerPtr lock, + unsigned int type, const char *name, + size_t nparams, + virLockManagerParamPtr params, + unsigned int flags) +{ + virLockManagerDlmPrivatePtr priv = lock->privateData; + char *newName = NULL; + + virCheckFlags(VIR_LOCK_MANAGER_RESOURCE_READONLY | + VIR_LOCK_MANAGER_RESOURCE_SHARED, -1); + + /* Treat read only resources as a no-op lock request */ + 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; + /* ignore disk resource without error */ + return 0; + } + } + + if (virCryptoHashString(VIR_CRYPTO_HASH_SHA256, name, &newName) < 0) + goto cleanup; + + break; + + case VIR_LOCK_MANAGER_RESOURCE_TYPE_LEASE: + /* we need format the lock information, so the lock name must be the constant length */ + if (virCryptoHashString(VIR_CRYPTO_HASH_SHA256, name, &newName) < 0) + goto cleanup; + + 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 cleanup; + + priv->resources[priv->nresources-1].name = newName; + + if (!!(flags & VIR_LOCK_MANAGER_RESOURCE_SHARED)) + priv->resources[priv->nresources-1].mode = LKM_PRMODE; + else + priv->resources[priv->nresources-1].mode = LKM_EXMODE; + + return 0; + + cleanup: + VIR_FREE(newName); + + return -1; +} + +static int virLockManagerDlmAcquire(virLockManagerPtr lock, + const char *state ATTRIBUTE_UNUSED, + unsigned int flags, + virDomainLockFailureAction action ATTRIBUTE_UNUSED, + int *fd) +{ + virLockManagerDlmPrivatePtr priv = lock->privateData; + virLockInformationPtr theLock = NULL; + struct dlm_lksb lksb = {0}; + int rv = -1, theFd = -1; + size_t i; + + virCheckFlags(VIR_LOCK_MANAGER_ACQUIRE_REGISTER_ONLY | + VIR_LOCK_MANAGER_ACQUIRE_RESTRICT, -1); + + /* allowed to start a guest which has read/write disks, but without any leases */ + 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; + } + + /* accorting to git patch history, add `fd` parameter in order to + * 'ensure sanlock socket is labelled with the VM process label', + * however, fixing sanlock socket security labelling remove related + * code. Now, `fd` parameter is useless. + */ + if (fd) + *fd = -1; + + if(!lockspace) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("lockspace is not opened")); + return -1; + } + + if (!(flags & VIR_LOCK_MANAGER_ACQUIRE_REGISTER_ONLY)) { + VIR_DEBUG("Acquiring object %zu", priv->nresources); + + theFd = open(driver->lockRecordFilePath, O_RDWR); + if (theFd < 0) { + virReportSystemError(errno, + _("unable to open '%s'"), driver->lockRecordFilePath); + return -1; + } + + for (i = 0; i < priv->nresources; i++) { + VIR_DEBUG("Acquiring object %zu", priv->nresources); + + memset(&lksb, 0, sizeof(lksb)); + rv = dlm_ls_lock_wait(lockspace, priv->resources[i].mode, + &lksb, LKF_NOQUEUE|LKF_PERSISTENT, + priv->resources[i].name, strlen(priv->resources[i].name), + 0, NULL, NULL, NULL); + /* both `rv` and `lksb.sb_status` equal 0 means lock sucessfully */ + if (rv || lksb.sb_status) { + if (lksb.sb_status == EAGAIN) + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to acquire lock: the lock could not be granted")); + else { + virReportSystemError(errno, + _("failed to acquire lock: rv=%d lockStatus=%d"), + rv, lksb.sb_status); + } + /* rv would be 0 although acquiring lock failed */ + rv = -1; + goto cleanup; + } + + theLock = virLockManagerDlmRecordLock(priv->resources[i].name, + priv->resources[i].mode, + lksb.sb_lkid, + priv->vm_pid); + if (!theLock) { + virReportSystemError(errno, + _("unable to record lock information, " + "lockName=%s lockMode=%s lockId=%d vm_pid=%jd"), + NULLSTR(priv->resources[i].name), + NULLSTR(virLockManagerDlmToModeText(priv->resources[i].mode)), + lksb.sb_lkid, (intmax_t)priv->vm_pid); + /* record lock failed, we can't save lock information in memory, so release it */ + rv = dlm_ls_unlock_wait(lockspace, lksb.sb_lkid, 0, &lksb); + if (!rv) + virReportSystemError(errno, + _("failed to release lock: rv=%d lockStatue=%d"), + rv, lksb.sb_status); + rv = -1; + goto cleanup; + } + + virMutexLock(&(lockListWait.fileMutex)); + virLockManagerDlmWriteLock(theLock, theFd, 1); + virMutexUnlock(&(lockListWait.fileMutex)); + } + + if(VIR_CLOSE(theFd) < 0) { + virReportSystemError(errno, + _("unable to save file '%s'"), + driver->lockRecordFilePath); + goto cleanup; + } + } + + if (flags & VIR_LOCK_MANAGER_ACQUIRE_RESTRICT) { + /* no daemon watches this fd, do nothing here, just close lockspace before `execv` + * `dlm_close_lockspace` always return 0, so ignore return value + */ + ignore_value(dlm_close_lockspace(lockspace)); + lockspace = NULL; + } + + rv = 0; + + cleanup: + if (rv) + VIR_FORCE_CLOSE(theFd); + return rv; +} + +static void virLockManagerDlmDeleteLock(const virLockInformationPtr lock, + const char *lockRecordFilePath) +{ + int fd = -1; + + if (!lock) + return; + + virMutexLock(&(lockListWait.listMutex)); + virListDelete(&(lock->entry)); + virMutexUnlock(&(lockListWait.listMutex)); + + fd = open(lockRecordFilePath, O_RDWR); + if (fd < 0) { + virReportSystemError(errno, + _("unable to open '%s'"), lockRecordFilePath); + goto cleanup; + } + + virMutexLock(&(lockListWait.fileMutex)); + virLockManagerDlmWriteLock(lock, fd, 0); + virMutexUnlock(&(lockListWait.fileMutex)); + + if (VIR_CLOSE(fd) < 0) { + virReportSystemError(errno, + _("unable to save file '%s'"), + lockRecordFilePath); + VIR_FORCE_CLOSE(fd); + } + + cleanup: + VIR_FREE(lock->name); + VIR_FREE(lock); +} + +static int virLockManagerDlmRelease(virLockManagerPtr lock, + char **state, + unsigned int flags) +{ + virLockManagerDlmPrivatePtr priv = lock->privateData; + virLockManagerDlmResourcePtr resource = NULL; + virLockInformationPtr theLock = NULL; + struct dlm_lksb lksb = {0}; + int rv = -1; + size_t i; + + virCheckFlags(0, -1); + + if(state) + *state = NULL; + + if(!lockspace) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("lockspace is not opened")); + return -1; + } + + for (i = 0; i < priv->nresources; i++) { + resource = priv->resources + i; + + virListForEachEntry (theLock, &(lockListWait.list), entry) { + if((theLock->vm_pid == priv->vm_pid) && + STREQ(theLock->name, resource->name) && + (theLock->mode == resource->mode)) { + + /* + * there are some locks from adopting, the existence of `(void *)1` + * when adopting makes 'terminated by signal SIGSEGV (Address + * boundary error)' error appear. + * + * The following code reference to lvm2 project's implement. + */ + lksb.sb_lkid = theLock->lkid; + rv = dlm_ls_lock_wait(lockspace, LKM_NLMODE, + &lksb, LKF_CONVERT, + resource->name, + strlen(resource->name), + 0, NULL, NULL, NULL); + + if (rv < 0) { + virReportSystemError(errno, + _("failed to convert lock: rv=%d lockStatus=%d"), + rv, lksb.sb_status); + goto cleanup; + } + + /* don't care whether the lock is released or not, + * it will be automatically released after the libvirtd dead + */ + virLockManagerDlmDeleteLock(theLock, driver->lockRecordFilePath); + + rv = dlm_ls_unlock_wait(lockspace, lksb.sb_lkid, 0, &lksb); + if (rv < 0) { + virReportSystemError(errno, + _("failed to release lock: rv=%d lockStatus=%d"), + rv, lksb.sb_status); + goto cleanup; + } + + break; + } + } + } + + rv = 0; + + cleanup: + return rv; +} + +static int virLockManagerDlmInquire(virLockManagerPtr lock ATTRIBUTE_UNUSED, + char **state, + unsigned int flags) +{ + /* not support mannual lock, so this function almost does nothing */ + virCheckFlags(0, -1); + + if (state) + *state = NULL; + + return 0; +} + +virLockDriver virLockDriverImpl = +{ + .version = VIR_LOCK_MANAGER_VERSION, + + .flags = VIR_LOCK_MANAGER_USES_STATE, // currently not used + + .drvInit = virLockManagerDlmInit, + .drvDeinit = virLockManagerDlmDeinit, + + .drvNew = virLockManagerDlmNew, + .drvFree = virLockManagerDlmFree, + + .drvAddResource = virLockManagerDlmAddResource, + + .drvAcquire = virLockManagerDlmAcquire, + .drvRelease = virLockManagerDlmRelease, + .drvInquire = virLockManagerDlmInquire, +}; diff --git a/src/util/virlist.h b/src/util/virlist.h new file mode 100644 index 000000000..4ac3626c7 --- /dev/null +++ b/src/util/virlist.h @@ -0,0 +1,110 @@ +/* + * virlist.h: methods for managing list, logic is copied from Linux Kernel + * + * Copyright (C) 2018 SUSE LINUX Products, Beijing, China. + * + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#ifndef __VIR_LIST_H +#define __VIR_LIST_H + +#include <stdbool.h> + +typedef struct _virListHead virListHead; +typedef virListHead *virListHeadPtr; + +struct _virListHead { + struct _virListHead *next, *prev; +}; + +static inline void +virListHeadInit(virListHeadPtr name) +{ + name->next = name; + name->prev = name; +} + +static inline void +__virListAdd(virListHeadPtr entry, + virListHeadPtr prev, virListHeadPtr next) +{ + next->prev = entry; + entry->next = next; + entry->prev = prev; + prev->next = entry; +} + +static inline void +virListAdd(virListHeadPtr entry, virListHeadPtr head) +{ + __virListAdd(entry, head, head->next); +} + +static inline void +virListAddTail(virListHeadPtr entry, virListHeadPtr head) +{ + __virListAdd(entry, head->prev, head); +} + +static inline void +__virListDelete(virListHeadPtr prev, virListHeadPtr next) +{ + next->prev = prev; + prev->next = next; +} + +static inline void +virListDelete(virListHeadPtr entry) +{ + __virListDelete(entry->prev, entry->next); +} + +static inline bool +virListEmpty(virListHeadPtr head) +{ + return head->next == head; +} + +#ifndef virContainerOf +#define virContainerOf(ptr, type, member) \ + (type *)((char *)(ptr) - (char *) &((type *)0)->member) +#endif + +#define virListEntry(ptr, type, member) \ + virContainerOf(ptr, type, member) + +#define virListFirstEntry(ptr, type, member) \ + virListEntry((ptr)->next, type, member) + +#define virListLastEntry(ptr, type, member) \ + virListEntry((ptr)->prev, type, member) + +#define __virContainerOf(ptr, sample, member) \ + (void *)virContainerOf((ptr), typeof(*(sample)), member) + +#define virListForEachEntry(pos, head, member) \ + for (pos = __virContainerOf((head)->next, pos, member); \ + &pos->member != (head); \ + pos = __virContainerOf(pos->member.next, pos, member)) + +#define virListForEachEntrySafe(pos, tmp, head, member) \ + for (pos = __virContainerOf((head)->next, pos, member), \ + tmp = __virContainerOf(pos->member.next, pos, member); \ + &pos->member != (head); \ + pos = tmp, tmp = __virContainerOf(pos->member.next, tmp, member)) + +#endif -- 2.15.1 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list