mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2025-01-08 22:15:21 +00:00
207709a031
The intent here is to allow the virt drivers to be run directly embedded in an arbitrary process without interfering with libvirtd. To achieve this they need to store all their configuration & state in a separate directory tree from the main system or session libvirtd instances. This can be useful for doing testing of the virt drivers in "make check" without interfering with the user's own libvirtd instances. It can also be used for applications using KVM/QEMU as a piece of infrastructure to build an service, rather than for general purpose OS hosting. A long standing example is libguestfs, which would prefer if its temporary VMs did show up in the main libvirtd VM list, because this confuses apps such as OpenStack Nova. A more recent example would be Kata which is using KVM as a technology to build containers. Reviewed-by: Michal Privoznik <mprivozn@redhat.com> Reviewed-by: Cole Robinson <crobinso@redhat.com> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
682 lines
17 KiB
C
682 lines
17 KiB
C
/*
|
|
* secret_driver.c: local driver for secret manipulation API
|
|
*
|
|
* Copyright (C) 2009-2016 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
|
|
* <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#include "internal.h"
|
|
#include "datatypes.h"
|
|
#include "driver.h"
|
|
#include "virlog.h"
|
|
#include "viralloc.h"
|
|
#include "secret_conf.h"
|
|
#include "virsecretobj.h"
|
|
#include "secret_driver.h"
|
|
#include "virthread.h"
|
|
#include "viruuid.h"
|
|
#include "virerror.h"
|
|
#include "virfile.h"
|
|
#include "virpidfile.h"
|
|
#include "configmake.h"
|
|
#include "virstring.h"
|
|
#include "viraccessapicheck.h"
|
|
#include "secret_event.h"
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_SECRET
|
|
|
|
VIR_LOG_INIT("secret.secret_driver");
|
|
|
|
enum { SECRET_MAX_XML_FILE = 10*1024*1024 };
|
|
|
|
/* Internal driver state */
|
|
|
|
typedef struct _virSecretDriverState virSecretDriverState;
|
|
typedef virSecretDriverState *virSecretDriverStatePtr;
|
|
struct _virSecretDriverState {
|
|
virMutex lock;
|
|
bool privileged; /* readonly */
|
|
virSecretObjListPtr secrets;
|
|
char *stateDir;
|
|
char *configDir;
|
|
|
|
/* pid file FD, ensures two copies of the driver can't use the same root */
|
|
int lockFD;
|
|
|
|
/* Immutable pointer, self-locking APIs */
|
|
virObjectEventStatePtr secretEventState;
|
|
};
|
|
|
|
static virSecretDriverStatePtr driver;
|
|
|
|
static void
|
|
secretDriverLock(void)
|
|
{
|
|
virMutexLock(&driver->lock);
|
|
}
|
|
|
|
|
|
static void
|
|
secretDriverUnlock(void)
|
|
{
|
|
virMutexUnlock(&driver->lock);
|
|
}
|
|
|
|
|
|
static virSecretObjPtr
|
|
secretObjFromSecret(virSecretPtr secret)
|
|
{
|
|
virSecretObjPtr obj;
|
|
char uuidstr[VIR_UUID_STRING_BUFLEN];
|
|
|
|
virUUIDFormat(secret->uuid, uuidstr);
|
|
if (!(obj = virSecretObjListFindByUUID(driver->secrets, uuidstr))) {
|
|
virReportError(VIR_ERR_NO_SECRET,
|
|
_("no secret with matching uuid '%s'"), uuidstr);
|
|
return NULL;
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
|
|
/* Driver functions */
|
|
|
|
static int
|
|
secretConnectNumOfSecrets(virConnectPtr conn)
|
|
{
|
|
if (virConnectNumOfSecretsEnsureACL(conn) < 0)
|
|
return -1;
|
|
|
|
return virSecretObjListNumOfSecrets(driver->secrets,
|
|
virConnectNumOfSecretsCheckACL,
|
|
conn);
|
|
}
|
|
|
|
|
|
static int
|
|
secretConnectListSecrets(virConnectPtr conn,
|
|
char **uuids,
|
|
int maxuuids)
|
|
{
|
|
memset(uuids, 0, maxuuids * sizeof(*uuids));
|
|
|
|
if (virConnectListSecretsEnsureACL(conn) < 0)
|
|
return -1;
|
|
|
|
return virSecretObjListGetUUIDs(driver->secrets, uuids, maxuuids,
|
|
virConnectListSecretsCheckACL, conn);
|
|
}
|
|
|
|
|
|
static int
|
|
secretConnectListAllSecrets(virConnectPtr conn,
|
|
virSecretPtr **secrets,
|
|
unsigned int flags)
|
|
{
|
|
virCheckFlags(VIR_CONNECT_LIST_SECRETS_FILTERS_ALL, -1);
|
|
|
|
if (virConnectListAllSecretsEnsureACL(conn) < 0)
|
|
return -1;
|
|
|
|
return virSecretObjListExport(conn, driver->secrets, secrets,
|
|
virConnectListAllSecretsCheckACL,
|
|
flags);
|
|
}
|
|
|
|
|
|
static virSecretPtr
|
|
secretLookupByUUID(virConnectPtr conn,
|
|
const unsigned char *uuid)
|
|
{
|
|
virSecretPtr ret = NULL;
|
|
virSecretObjPtr obj;
|
|
virSecretDefPtr def;
|
|
char uuidstr[VIR_UUID_STRING_BUFLEN];
|
|
|
|
virUUIDFormat(uuid, uuidstr);
|
|
if (!(obj = virSecretObjListFindByUUID(driver->secrets, uuidstr))) {
|
|
virReportError(VIR_ERR_NO_SECRET,
|
|
_("no secret with matching uuid '%s'"), uuidstr);
|
|
goto cleanup;
|
|
}
|
|
|
|
def = virSecretObjGetDef(obj);
|
|
if (virSecretLookupByUUIDEnsureACL(conn, def) < 0)
|
|
goto cleanup;
|
|
|
|
ret = virGetSecret(conn,
|
|
def->uuid,
|
|
def->usage_type,
|
|
def->usage_id);
|
|
|
|
cleanup:
|
|
virSecretObjEndAPI(&obj);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static virSecretPtr
|
|
secretLookupByUsage(virConnectPtr conn,
|
|
int usageType,
|
|
const char *usageID)
|
|
{
|
|
virSecretPtr ret = NULL;
|
|
virSecretObjPtr obj;
|
|
virSecretDefPtr def;
|
|
|
|
if (!(obj = virSecretObjListFindByUsage(driver->secrets,
|
|
usageType, usageID))) {
|
|
virReportError(VIR_ERR_NO_SECRET,
|
|
_("no secret with matching usage '%s'"), usageID);
|
|
goto cleanup;
|
|
}
|
|
|
|
def = virSecretObjGetDef(obj);
|
|
if (virSecretLookupByUsageEnsureACL(conn, def) < 0)
|
|
goto cleanup;
|
|
|
|
ret = virGetSecret(conn,
|
|
def->uuid,
|
|
def->usage_type,
|
|
def->usage_id);
|
|
|
|
cleanup:
|
|
virSecretObjEndAPI(&obj);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static virSecretPtr
|
|
secretDefineXML(virConnectPtr conn,
|
|
const char *xml,
|
|
unsigned int flags)
|
|
{
|
|
virSecretPtr ret = NULL;
|
|
virSecretObjPtr obj = NULL;
|
|
virSecretDefPtr objDef;
|
|
virSecretDefPtr backup = NULL;
|
|
virSecretDefPtr def;
|
|
virObjectEventPtr event = NULL;
|
|
|
|
virCheckFlags(0, NULL);
|
|
|
|
if (!(def = virSecretDefParseString(xml)))
|
|
return NULL;
|
|
|
|
if (virSecretDefineXMLEnsureACL(conn, def) < 0)
|
|
goto cleanup;
|
|
|
|
if (!(obj = virSecretObjListAdd(driver->secrets, def,
|
|
driver->configDir, &backup)))
|
|
goto cleanup;
|
|
objDef = g_steal_pointer(&def);
|
|
|
|
if (!objDef->isephemeral) {
|
|
if (backup && backup->isephemeral) {
|
|
if (virSecretObjSaveData(obj) < 0)
|
|
goto restore_backup;
|
|
}
|
|
|
|
if (virSecretObjSaveConfig(obj) < 0) {
|
|
if (backup && backup->isephemeral) {
|
|
/* Undo the virSecretObjSaveData() above; ignore errors */
|
|
virSecretObjDeleteData(obj);
|
|
}
|
|
goto restore_backup;
|
|
}
|
|
} else if (backup && !backup->isephemeral) {
|
|
if (virSecretObjDeleteConfig(obj) < 0)
|
|
goto restore_backup;
|
|
|
|
virSecretObjDeleteData(obj);
|
|
}
|
|
/* Saved successfully - drop old values */
|
|
virSecretDefFree(backup);
|
|
|
|
event = virSecretEventLifecycleNew(objDef->uuid,
|
|
objDef->usage_type,
|
|
objDef->usage_id,
|
|
VIR_SECRET_EVENT_DEFINED,
|
|
0);
|
|
|
|
ret = virGetSecret(conn,
|
|
objDef->uuid,
|
|
objDef->usage_type,
|
|
objDef->usage_id);
|
|
goto cleanup;
|
|
|
|
restore_backup:
|
|
/* If we have a backup, then secret was defined before, so just restore
|
|
* the backup; otherwise, this is a new secret, thus remove it. */
|
|
if (backup) {
|
|
virSecretObjSetDef(obj, backup);
|
|
def = g_steal_pointer(&objDef);
|
|
} else {
|
|
virSecretObjListRemove(driver->secrets, obj);
|
|
virObjectUnref(obj);
|
|
obj = NULL;
|
|
}
|
|
|
|
cleanup:
|
|
virSecretDefFree(def);
|
|
virSecretObjEndAPI(&obj);
|
|
virObjectEventStateQueue(driver->secretEventState, event);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static char *
|
|
secretGetXMLDesc(virSecretPtr secret,
|
|
unsigned int flags)
|
|
{
|
|
char *ret = NULL;
|
|
virSecretObjPtr obj;
|
|
virSecretDefPtr def;
|
|
|
|
virCheckFlags(0, NULL);
|
|
|
|
if (!(obj = secretObjFromSecret(secret)))
|
|
goto cleanup;
|
|
|
|
def = virSecretObjGetDef(obj);
|
|
if (virSecretGetXMLDescEnsureACL(secret->conn, def) < 0)
|
|
goto cleanup;
|
|
|
|
ret = virSecretDefFormat(def);
|
|
|
|
cleanup:
|
|
virSecretObjEndAPI(&obj);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
secretSetValue(virSecretPtr secret,
|
|
const unsigned char *value,
|
|
size_t value_size,
|
|
unsigned int flags)
|
|
{
|
|
int ret = -1;
|
|
virSecretObjPtr obj;
|
|
virSecretDefPtr def;
|
|
virObjectEventPtr event = NULL;
|
|
|
|
virCheckFlags(0, -1);
|
|
|
|
if (!(obj = secretObjFromSecret(secret)))
|
|
goto cleanup;
|
|
|
|
def = virSecretObjGetDef(obj);
|
|
if (virSecretSetValueEnsureACL(secret->conn, def) < 0)
|
|
goto cleanup;
|
|
|
|
if (virSecretObjSetValue(obj, value, value_size) < 0)
|
|
goto cleanup;
|
|
|
|
event = virSecretEventValueChangedNew(def->uuid,
|
|
def->usage_type,
|
|
def->usage_id);
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
virSecretObjEndAPI(&obj);
|
|
virObjectEventStateQueue(driver->secretEventState, event);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static unsigned char *
|
|
secretGetValue(virSecretPtr secret,
|
|
size_t *value_size,
|
|
unsigned int flags,
|
|
unsigned int internalFlags)
|
|
{
|
|
unsigned char *ret = NULL;
|
|
virSecretObjPtr obj;
|
|
virSecretDefPtr def;
|
|
|
|
virCheckFlags(0, NULL);
|
|
|
|
if (!(obj = secretObjFromSecret(secret)))
|
|
goto cleanup;
|
|
|
|
def = virSecretObjGetDef(obj);
|
|
if (virSecretGetValueEnsureACL(secret->conn, def) < 0)
|
|
goto cleanup;
|
|
|
|
if ((internalFlags & VIR_SECRET_GET_VALUE_INTERNAL_CALL) == 0 &&
|
|
def->isprivate) {
|
|
virReportError(VIR_ERR_INVALID_SECRET, "%s",
|
|
_("secret is private"));
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!(ret = virSecretObjGetValue(obj)))
|
|
goto cleanup;
|
|
|
|
*value_size = virSecretObjGetValueSize(obj);
|
|
|
|
cleanup:
|
|
virSecretObjEndAPI(&obj);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
secretUndefine(virSecretPtr secret)
|
|
{
|
|
int ret = -1;
|
|
virSecretObjPtr obj;
|
|
virSecretDefPtr def;
|
|
virObjectEventPtr event = NULL;
|
|
|
|
if (!(obj = secretObjFromSecret(secret)))
|
|
goto cleanup;
|
|
|
|
def = virSecretObjGetDef(obj);
|
|
if (virSecretUndefineEnsureACL(secret->conn, def) < 0)
|
|
goto cleanup;
|
|
|
|
if (virSecretObjDeleteConfig(obj) < 0)
|
|
goto cleanup;
|
|
|
|
event = virSecretEventLifecycleNew(def->uuid,
|
|
def->usage_type,
|
|
def->usage_id,
|
|
VIR_SECRET_EVENT_UNDEFINED,
|
|
0);
|
|
|
|
virSecretObjDeleteData(obj);
|
|
|
|
virSecretObjListRemove(driver->secrets, obj);
|
|
virObjectUnref(obj);
|
|
obj = NULL;
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
virSecretObjEndAPI(&obj);
|
|
virObjectEventStateQueue(driver->secretEventState, event);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
secretStateCleanup(void)
|
|
{
|
|
if (!driver)
|
|
return -1;
|
|
|
|
secretDriverLock();
|
|
|
|
virObjectUnref(driver->secrets);
|
|
VIR_FREE(driver->configDir);
|
|
|
|
virObjectUnref(driver->secretEventState);
|
|
|
|
if (driver->lockFD != -1)
|
|
virPidFileRelease(driver->stateDir, "driver", driver->lockFD);
|
|
|
|
VIR_FREE(driver->stateDir);
|
|
secretDriverUnlock();
|
|
virMutexDestroy(&driver->lock);
|
|
VIR_FREE(driver);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
secretStateInitialize(bool privileged,
|
|
const char *root,
|
|
virStateInhibitCallback callback G_GNUC_UNUSED,
|
|
void *opaque G_GNUC_UNUSED)
|
|
{
|
|
if (root != NULL) {
|
|
virReportError(VIR_ERR_INVALID_ARG, "%s",
|
|
_("Driver does not support embedded mode"));
|
|
return -1;
|
|
}
|
|
|
|
if (VIR_ALLOC(driver) < 0)
|
|
return VIR_DRV_STATE_INIT_ERROR;
|
|
|
|
driver->lockFD = -1;
|
|
if (virMutexInit(&driver->lock) < 0) {
|
|
VIR_FREE(driver);
|
|
return VIR_DRV_STATE_INIT_ERROR;
|
|
}
|
|
secretDriverLock();
|
|
|
|
driver->secretEventState = virObjectEventStateNew();
|
|
driver->privileged = privileged;
|
|
|
|
if (privileged) {
|
|
driver->configDir = g_strdup_printf("%s/libvirt/secrets", SYSCONFDIR);
|
|
driver->stateDir = g_strdup_printf("%s/libvirt/secrets", RUNSTATEDIR);
|
|
} else {
|
|
g_autofree char *rundir = NULL;
|
|
g_autofree char *cfgdir = NULL;
|
|
|
|
cfgdir = virGetUserConfigDirectory();
|
|
driver->configDir = g_strdup_printf("%s/secrets/", cfgdir);
|
|
|
|
rundir = virGetUserRuntimeDirectory();
|
|
driver->stateDir = g_strdup_printf("%s/secrets/run", rundir);
|
|
}
|
|
|
|
if (virFileMakePathWithMode(driver->configDir, S_IRWXU) < 0) {
|
|
virReportSystemError(errno, _("cannot create config directory '%s'"),
|
|
driver->configDir);
|
|
goto error;
|
|
}
|
|
|
|
if (virFileMakePathWithMode(driver->stateDir, S_IRWXU) < 0) {
|
|
virReportSystemError(errno, _("cannot create state directory '%s'"),
|
|
driver->stateDir);
|
|
goto error;
|
|
}
|
|
|
|
if ((driver->lockFD =
|
|
virPidFileAcquire(driver->stateDir, "driver", false, getpid())) < 0)
|
|
goto error;
|
|
|
|
if (!(driver->secrets = virSecretObjListNew()))
|
|
goto error;
|
|
|
|
if (virSecretLoadAllConfigs(driver->secrets, driver->configDir) < 0)
|
|
goto error;
|
|
|
|
secretDriverUnlock();
|
|
return VIR_DRV_STATE_INIT_COMPLETE;
|
|
|
|
error:
|
|
secretDriverUnlock();
|
|
secretStateCleanup();
|
|
return VIR_DRV_STATE_INIT_ERROR;
|
|
}
|
|
|
|
|
|
static int
|
|
secretStateReload(void)
|
|
{
|
|
if (!driver)
|
|
return -1;
|
|
|
|
secretDriverLock();
|
|
|
|
ignore_value(virSecretLoadAllConfigs(driver->secrets, driver->configDir));
|
|
|
|
secretDriverUnlock();
|
|
return 0;
|
|
}
|
|
|
|
|
|
static virDrvOpenStatus
|
|
secretConnectOpen(virConnectPtr conn,
|
|
virConnectAuthPtr auth G_GNUC_UNUSED,
|
|
virConfPtr conf G_GNUC_UNUSED,
|
|
unsigned int flags)
|
|
{
|
|
virCheckFlags(VIR_CONNECT_RO, VIR_DRV_OPEN_ERROR);
|
|
|
|
if (driver == NULL) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("secret state driver is not active"));
|
|
return VIR_DRV_OPEN_ERROR;
|
|
}
|
|
|
|
if (!virConnectValidateURIPath(conn->uri->path,
|
|
"secret",
|
|
driver->privileged))
|
|
return VIR_DRV_OPEN_ERROR;
|
|
|
|
if (virConnectOpenEnsureACL(conn) < 0)
|
|
return VIR_DRV_OPEN_ERROR;
|
|
|
|
return VIR_DRV_OPEN_SUCCESS;
|
|
}
|
|
|
|
static int secretConnectClose(virConnectPtr conn G_GNUC_UNUSED)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int secretConnectIsSecure(virConnectPtr conn G_GNUC_UNUSED)
|
|
{
|
|
/* Trivially secure, since always inside the daemon */
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int secretConnectIsEncrypted(virConnectPtr conn G_GNUC_UNUSED)
|
|
{
|
|
/* Not encrypted, but remote driver takes care of that */
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int secretConnectIsAlive(virConnectPtr conn G_GNUC_UNUSED)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int
|
|
secretConnectSecretEventRegisterAny(virConnectPtr conn,
|
|
virSecretPtr secret,
|
|
int eventID,
|
|
virConnectSecretEventGenericCallback callback,
|
|
void *opaque,
|
|
virFreeCallback freecb)
|
|
{
|
|
int callbackID = -1;
|
|
|
|
if (virConnectSecretEventRegisterAnyEnsureACL(conn) < 0)
|
|
return -1;
|
|
|
|
if (virSecretEventStateRegisterID(conn, driver->secretEventState,
|
|
secret, eventID, callback,
|
|
opaque, freecb, &callbackID) < 0)
|
|
callbackID = -1;
|
|
|
|
return callbackID;
|
|
}
|
|
|
|
|
|
static int
|
|
secretConnectSecretEventDeregisterAny(virConnectPtr conn,
|
|
int callbackID)
|
|
{
|
|
if (virConnectSecretEventDeregisterAnyEnsureACL(conn) < 0)
|
|
return -1;
|
|
|
|
if (virObjectEventStateDeregisterID(conn,
|
|
driver->secretEventState,
|
|
callbackID, true) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static virSecretDriver secretDriver = {
|
|
.name = "secret",
|
|
.connectNumOfSecrets = secretConnectNumOfSecrets, /* 0.7.1 */
|
|
.connectListSecrets = secretConnectListSecrets, /* 0.7.1 */
|
|
.connectListAllSecrets = secretConnectListAllSecrets, /* 0.10.2 */
|
|
.secretLookupByUUID = secretLookupByUUID, /* 0.7.1 */
|
|
.secretLookupByUsage = secretLookupByUsage, /* 0.7.1 */
|
|
.secretDefineXML = secretDefineXML, /* 0.7.1 */
|
|
.secretGetXMLDesc = secretGetXMLDesc, /* 0.7.1 */
|
|
.secretSetValue = secretSetValue, /* 0.7.1 */
|
|
.secretGetValue = secretGetValue, /* 0.7.1 */
|
|
.secretUndefine = secretUndefine, /* 0.7.1 */
|
|
.connectSecretEventRegisterAny = secretConnectSecretEventRegisterAny, /* 3.0.0 */
|
|
.connectSecretEventDeregisterAny = secretConnectSecretEventDeregisterAny, /* 3.0.0 */
|
|
};
|
|
|
|
|
|
static virHypervisorDriver secretHypervisorDriver = {
|
|
.name = "secret",
|
|
.connectOpen = secretConnectOpen, /* 4.1.0 */
|
|
.connectClose = secretConnectClose, /* 4.1.0 */
|
|
.connectIsEncrypted = secretConnectIsEncrypted, /* 4.1.0 */
|
|
.connectIsSecure = secretConnectIsSecure, /* 4.1.0 */
|
|
.connectIsAlive = secretConnectIsAlive, /* 4.1.0 */
|
|
};
|
|
|
|
|
|
static virConnectDriver secretConnectDriver = {
|
|
.localOnly = true,
|
|
.uriSchemes = (const char *[]){ "secret", NULL },
|
|
.hypervisorDriver = &secretHypervisorDriver,
|
|
.secretDriver = &secretDriver,
|
|
};
|
|
|
|
|
|
static virStateDriver stateDriver = {
|
|
.name = "secret",
|
|
.stateInitialize = secretStateInitialize,
|
|
.stateCleanup = secretStateCleanup,
|
|
.stateReload = secretStateReload,
|
|
};
|
|
|
|
|
|
int
|
|
secretRegister(void)
|
|
{
|
|
if (virRegisterConnectDriver(&secretConnectDriver, false) < 0)
|
|
return -1;
|
|
if (virSetSharedSecretDriver(&secretDriver) < 0)
|
|
return -1;
|
|
if (virRegisterStateDriver(&stateDriver) < 0)
|
|
return -1;
|
|
return 0;
|
|
}
|