[libvirt] [PATCH 00/23] Introduce a virtlockd daemon for disk locking

This is a long overdue update to a patch series I posted about a year ago https://www.redhat.com/archives/libvir-list/2011-July/msg00337.html There have been some major changes since that series - A general purpose lockspace module has been created (virLockSpacePtr in src/util/virlockspace.[ch]) - The virtlockd daemon protocol has been re-written so it only operates at the level of virLockSpacePtr APIs, and knows nothing of the usage wrt to virDomainObjPtrs - The lock driver client now translates requests for disk locks on a virDomainObjPtr into requests for resources in a lockspace managed by virtlockd. - The virtlockd daemon now has the ability to re-exec() itself to upgrade software without loosing active locks or clients - By default the locks are held directly on the file paths, rather than in a parallel "locks" directory based on sha256 checksums of the filename Still todo - Add ability to quiesce all server/client I/O when doing re-exec() - Add ability to save/restore data in any virNetMessagePtr structs in the client rx or tx queues - Add ability to use custom lockspaces for LVM and SCSI/ISCSI block devices, instead of locking based on path, to gain cross-node safety, instead of node-local safety. NB, the current re-exec() support works, but is not race safe without those first 2 todo items being completed .gitignore | 5 cfg.mk | 9 daemon/libvirtd.c | 2 daemon/remote.c | 15 daemon/remote.h | 6 include/libvirt/virterror.h | 2 libvirt.spec.in | 16 po/POTFILES.in | 5 src/Makefile.am | 182 ++++- src/internal.h | 22 src/libvirt_private.syms | 32 src/locking/domain_lock.c | 26 src/locking/lock_daemon.c | 1336 +++++++++++++++++++++++++++++++++++++ src/locking/lock_daemon.h | 56 + src/locking/lock_daemon_dispatch.c | 370 ++++++++++ src/locking/lock_daemon_dispatch.h | 31 src/locking/lock_driver_lockd.c | 561 +++++++++++++++ src/locking/lock_manager.c | 31 src/locking/lock_manager.h | 3 src/locking/lock_protocol.x | 89 ++ src/locking/virtlockd.init.in | 93 ++ src/locking/virtlockd.service.in | 13 src/locking/virtlockd.socket.in | 8 src/locking/virtlockd.sysconf | 3 src/lxc/lxc_controller.c | 24 src/lxc/lxc_monitor.c | 2 src/qemu/qemu.conf | 17 src/qemu/qemu_agent.c | 10 src/qemu/qemu_conf.c | 2 src/qemu/qemu_monitor_json.c | 12 src/qemu/test_libvirtd_qemu.aug.in | 2 src/remote/remote_driver.c | 9 src/rpc/virnetclient.c | 81 +- src/rpc/virnetclient.h | 3 src/rpc/virnetserver.c | 308 ++++++++ src/rpc/virnetserver.h | 20 src/rpc/virnetserverclient.c | 198 ++++- src/rpc/virnetserverclient.h | 28 src/rpc/virnetserverservice.c | 211 +++++ src/rpc/virnetserverservice.h | 13 src/rpc/virnetsocket.c | 128 +++ src/rpc/virnetsocket.h | 9 src/util/json.c | 9 src/util/json.h | 3 src/util/threadpool.c | 19 src/util/threadpool.h | 4 src/util/virlockspace.c | 784 +++++++++++++++++++++ src/util/virlockspace.h | 62 + src/util/virterror.c | 9 tests/Makefile.am | 7 tests/virlockspacetest.c | 363 ++++++++++ 51 files changed, 5080 insertions(+), 173 deletions(-)

From: "Daniel P. Berrange" <berrange@redhat.com> To allow a virLockManagerPtr to be created directly from a driver table struct, replace the virLockManagerPluginPtr parameter with a virLockDriverPtr parameter. * src/locking/domain_lock.c, src/locking/lock_manager.c, src/locking/lock_manager.h: Replace plugin param with a driver in virLockManagerNew Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/locking/domain_lock.c | 2 +- src/locking/lock_manager.c | 31 +++++++++++++++++++------------ src/locking/lock_manager.h | 3 ++- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/locking/domain_lock.c b/src/locking/domain_lock.c index 55f5640..ba49f9c 100644 --- a/src/locking/domain_lock.c +++ b/src/locking/domain_lock.c @@ -124,7 +124,7 @@ static virLockManagerPtr virDomainLockManagerNew(virLockManagerPluginPtr plugin, memcpy(params[0].value.uuid, dom->def->uuid, VIR_UUID_BUFLEN); - if (!(lock = virLockManagerNew(plugin, + if (!(lock = virLockManagerNew(virLockManagerPluginGetDriver(plugin), VIR_LOCK_MANAGER_OBJECT_TYPE_DOMAIN, ARRAY_CARDINALITY(params), params, diff --git a/src/locking/lock_manager.c b/src/locking/lock_manager.c index 18e739e..1d9c1bf 100644 --- a/src/locking/lock_manager.c +++ b/src/locking/lock_manager.c @@ -39,11 +39,11 @@ #define VIR_FROM_THIS VIR_FROM_LOCKING -#define CHECK_PLUGIN(field, errret) \ - if (!plugin->driver->field) { \ - virReportError(VIR_ERR_INTERNAL_ERROR, \ - _("Missing '%s' field in lock manager driver"), \ - #field); \ +#define CHECK_DRIVER(field, errret) \ + if (!driver->field) { \ + virReportError(VIR_ERR_INTERNAL_ERROR, \ + _("Missing '%s' field in lock manager driver"), \ + #field); \ return errret; \ } @@ -265,9 +265,16 @@ bool virLockManagerPluginUsesState(virLockManagerPluginPtr plugin) } +virLockDriverPtr virLockManagerPluginGetDriver(virLockManagerPluginPtr plugin) +{ + VIR_DEBUG("plugin=%p", plugin); + + return plugin->driver; +} + /** * virLockManagerNew: - * @plugin: the plugin implementation to use + * @driver: the lock manager implementation to use * @type: the type of process to be supervised * @flags: optional flags, currently unused * @@ -276,27 +283,27 @@ bool virLockManagerPluginUsesState(virLockManagerPluginPtr plugin) * * Returns a new lock manager context */ -virLockManagerPtr virLockManagerNew(virLockManagerPluginPtr plugin, +virLockManagerPtr virLockManagerNew(virLockDriverPtr driver, unsigned int type, size_t nparams, virLockManagerParamPtr params, unsigned int flags) { virLockManagerPtr lock; - VIR_DEBUG("plugin=%p type=%u nparams=%zu params=%p flags=%x", - plugin, type, nparams, params, flags); + VIR_DEBUG("driver=%p type=%u nparams=%zu params=%p flags=%x", + driver, type, nparams, params, flags); virLockManagerLogParams(nparams, params); - CHECK_PLUGIN(drvNew, NULL); + CHECK_DRIVER(drvNew, NULL); if (VIR_ALLOC(lock) < 0) { virReportOOMError(); return NULL; } - lock->driver = plugin->driver; + lock->driver = driver; - if (plugin->driver->drvNew(lock, type, nparams, params, flags) < 0) { + if (driver->drvNew(lock, type, nparams, params, flags) < 0) { VIR_FREE(lock); return NULL; } diff --git a/src/locking/lock_manager.h b/src/locking/lock_manager.h index b548d45..25c7f7f 100644 --- a/src/locking/lock_manager.h +++ b/src/locking/lock_manager.h @@ -37,8 +37,9 @@ void virLockManagerPluginUnref(virLockManagerPluginPtr plugin); const char *virLockManagerPluginGetName(virLockManagerPluginPtr plugin); bool virLockManagerPluginUsesState(virLockManagerPluginPtr plugin); +virLockDriverPtr virLockManagerPluginGetDriver(virLockManagerPluginPtr plugin); -virLockManagerPtr virLockManagerNew(virLockManagerPluginPtr plugin, +virLockManagerPtr virLockManagerNew(virLockDriverPtr driver, unsigned int type, size_t nparams, virLockManagerParamPtr params, -- 1.7.11.2

On 08/09/2012 09:20 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
To allow a virLockManagerPtr to be created directly from a driver table struct, replace the virLockManagerPluginPtr parameter with a virLockDriverPtr parameter.
* src/locking/domain_lock.c, src/locking/lock_manager.c, src/locking/lock_manager.h: Replace plugin param with a driver in virLockManagerNew
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/locking/domain_lock.c | 2 +- src/locking/lock_manager.c | 31 +++++++++++++++++++------------ src/locking/lock_manager.h | 3 ++- 3 files changed, 22 insertions(+), 14 deletions(-)
ACK. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

From: "Daniel P. Berrange" <berrange@redhat.com> While the QEMU monitor/agent do not want JSON strings pretty printed, other parts of libvirt might. Instead of hardcoding QEMU's desired behaviour in virJSONValueToString(), add a boolean flag to control pretty printing Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/qemu/qemu_agent.c | 10 +++++----- src/qemu/qemu_monitor_json.c | 12 ++++++------ src/util/json.c | 9 +++++---- src/util/json.h | 3 ++- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/qemu/qemu_agent.c b/src/qemu/qemu_agent.c index 15af758..513f1d5 100644 --- a/src/qemu/qemu_agent.c +++ b/src/qemu/qemu_agent.c @@ -993,7 +993,7 @@ qemuAgentCommand(qemuAgentPtr mon, memset(&msg, 0, sizeof(msg)); - if (!(cmdstr = virJSONValueToString(cmd))) { + if (!(cmdstr = virJSONValueToString(cmd, false))) { virReportOOMError(); goto cleanup; } @@ -1107,8 +1107,8 @@ qemuAgentCheckError(virJSONValuePtr cmd, { if (virJSONValueObjectHasKey(reply, "error")) { virJSONValuePtr error = virJSONValueObjectGet(reply, "error"); - char *cmdstr = virJSONValueToString(cmd); - char *replystr = virJSONValueToString(reply); + char *cmdstr = virJSONValueToString(cmd, false); + char *replystr = virJSONValueToString(reply, false); /* Log the full JSON formatted command & error */ VIR_DEBUG("unable to execute QEMU command %s: %s", @@ -1129,8 +1129,8 @@ qemuAgentCheckError(virJSONValuePtr cmd, VIR_FREE(replystr); return -1; } else if (!virJSONValueObjectHasKey(reply, "return")) { - char *cmdstr = virJSONValueToString(cmd); - char *replystr = virJSONValueToString(reply); + char *cmdstr = virJSONValueToString(cmd, false); + char *replystr = virJSONValueToString(reply, false); VIR_DEBUG("Neither 'return' nor 'error' is set in the JSON reply %s: %s", cmdstr, replystr); diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index 3ede88d..14842e9 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -243,7 +243,7 @@ qemuMonitorJSONCommandWithFd(qemuMonitorPtr mon, } } - if (!(cmdstr = virJSONValueToString(cmd))) { + if (!(cmdstr = virJSONValueToString(cmd, false))) { virReportOOMError(); goto cleanup; } @@ -328,8 +328,8 @@ qemuMonitorJSONCheckError(virJSONValuePtr cmd, { if (virJSONValueObjectHasKey(reply, "error")) { virJSONValuePtr error = virJSONValueObjectGet(reply, "error"); - char *cmdstr = virJSONValueToString(cmd); - char *replystr = virJSONValueToString(reply); + char *cmdstr = virJSONValueToString(cmd, false); + char *replystr = virJSONValueToString(reply, false); /* Log the full JSON formatted command & error */ VIR_DEBUG("unable to execute QEMU command %s: %s", @@ -350,8 +350,8 @@ qemuMonitorJSONCheckError(virJSONValuePtr cmd, VIR_FREE(replystr); return -1; } else if (!virJSONValueObjectHasKey(reply, "return")) { - char *cmdstr = virJSONValueToString(cmd); - char *replystr = virJSONValueToString(reply); + char *cmdstr = virJSONValueToString(cmd, false); + char *replystr = virJSONValueToString(reply, false); VIR_DEBUG("Neither 'return' nor 'error' is set in the JSON reply %s: %s", cmdstr, replystr); @@ -3354,7 +3354,7 @@ int qemuMonitorJSONArbitraryCommand(qemuMonitorPtr mon, if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0) goto cleanup; - if (!(*reply_str = virJSONValueToString(reply))) + if (!(*reply_str = virJSONValueToString(reply, false))) goto cleanup; } diff --git a/src/util/json.c b/src/util/json.c index 5132989..8687a16 100644 --- a/src/util/json.c +++ b/src/util/json.c @@ -1054,14 +1054,15 @@ static int virJSONValueToStringOne(virJSONValuePtr object, return 0; } -char *virJSONValueToString(virJSONValuePtr object) +char *virJSONValueToString(virJSONValuePtr object, + bool pretty) { yajl_gen g; const unsigned char *str; char *ret = NULL; yajl_size_t len; # ifndef HAVE_YAJL2 - yajl_gen_config conf = { 0, " " }; /* Turns off pretty printing since QEMU can't cope */ + yajl_gen_config conf = { pretty ? 1 : 0, pretty ? " " : " "}; # endif VIR_DEBUG("object=%p", object); @@ -1069,8 +1070,8 @@ char *virJSONValueToString(virJSONValuePtr object) # ifdef HAVE_YAJL2 g = yajl_gen_alloc(NULL); if (g) { - yajl_gen_config(g, yajl_gen_beautify, 0); - yajl_gen_config(g, yajl_gen_indent_string, " "); + yajl_gen_config(g, yajl_gen_beautify, pretty ? 1 : 0); + yajl_gen_config(g, yajl_gen_indent_string, pretty ? " " : " "); yajl_gen_config(g, yajl_gen_validate_utf8, 1); } # else diff --git a/src/util/json.h b/src/util/json.h index c8bd504..bdba3dd 100644 --- a/src/util/json.h +++ b/src/util/json.h @@ -132,6 +132,7 @@ int virJSONValueObjectAppendBoolean(virJSONValuePtr object, const char *key, int int virJSONValueObjectAppendNull(virJSONValuePtr object, const char *key); virJSONValuePtr virJSONValueFromString(const char *jsonstring); -char *virJSONValueToString(virJSONValuePtr object); +char *virJSONValueToString(virJSONValuePtr object, + bool pretty); #endif /* __VIR_JSON_H_ */ -- 1.7.11.2

On 08/09/2012 09:20 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
While the QEMU monitor/agent do not want JSON strings pretty printed, other parts of libvirt might. Instead of hardcoding QEMU's desired behaviour in virJSONValueToString(), add a boolean flag to control pretty printing
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/qemu/qemu_agent.c | 10 +++++----- src/qemu/qemu_monitor_json.c | 12 ++++++------ src/util/json.c | 9 +++++---- src/util/json.h | 3 ++- 4 files changed, 18 insertions(+), 16 deletions(-)
ACK. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

From: "Daniel P. Berrange" <berrange@redhat.com> It is desirable to be able to query the config params of the thread pool, in order to save the server state. Add virThreadPoolGetMinWorkers, virThreadPoolGetMaxWorkers and virThreadPoolGetPriorityWorkers APIs. Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/libvirt_private.syms | 3 +++ src/util/threadpool.c | 19 +++++++++++++++++++ src/util/threadpool.h | 4 ++++ 3 files changed, 26 insertions(+) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 79b4a18..3a23fe6 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1085,6 +1085,9 @@ virSysinfoRead; virThreadPoolFree; virThreadPoolNew; virThreadPoolSendJob; +virThreadPoolGetMinWorkers; +virThreadPoolGetMaxWorkers; +virThreadPoolGetPriorityWorkers; # threads.h diff --git a/src/util/threadpool.c b/src/util/threadpool.c index 63c5ea0..2ae11b8 100644 --- a/src/util/threadpool.c +++ b/src/util/threadpool.c @@ -66,6 +66,7 @@ struct _virThreadPool { virCond quit_cond; size_t maxWorkers; + size_t minWorkers; size_t freeWorkers; size_t nWorkers; virThreadPtr workers; @@ -188,7 +189,9 @@ virThreadPoolPtr virThreadPoolNew(size_t minWorkers, if (VIR_ALLOC_N(pool->workers, minWorkers) < 0) goto error; + pool->minWorkers = minWorkers; pool->maxWorkers = maxWorkers; + for (i = 0; i < minWorkers; i++) { if (VIR_ALLOC(data) < 0) { virReportOOMError(); @@ -277,6 +280,22 @@ void virThreadPoolFree(virThreadPoolPtr pool) VIR_FREE(pool); } + +size_t virThreadPoolGetMinWorkers(virThreadPoolPtr pool) +{ + return pool->minWorkers; +} + +size_t virThreadPoolGetMaxWorkers(virThreadPoolPtr pool) +{ + return pool->maxWorkers; +} + +size_t virThreadPoolGetPriorityWorkers(virThreadPoolPtr pool) +{ + return pool->nPrioWorkers; +} + /* * @priority - job priority * Return: 0 on success, -1 otherwise diff --git a/src/util/threadpool.h b/src/util/threadpool.h index 894b278..798fd0b 100644 --- a/src/util/threadpool.h +++ b/src/util/threadpool.h @@ -39,6 +39,10 @@ virThreadPoolPtr virThreadPoolNew(size_t minWorkers, virThreadPoolJobFunc func, void *opaque) ATTRIBUTE_NONNULL(4); +size_t virThreadPoolGetMinWorkers(virThreadPoolPtr pool); +size_t virThreadPoolGetMaxWorkers(virThreadPoolPtr pool); +size_t virThreadPoolGetPriorityWorkers(virThreadPoolPtr pool); + void virThreadPoolFree(virThreadPoolPtr pool); int virThreadPoolSendJob(virThreadPoolPtr pool, -- 1.7.11.2

On 08/09/2012 09:20 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
It is desirable to be able to query the config params of the thread pool, in order to save the server state. Add virThreadPoolGetMinWorkers, virThreadPoolGetMaxWorkers and virThreadPoolGetPriorityWorkers APIs.
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/libvirt_private.syms | 3 +++ src/util/threadpool.c | 19 +++++++++++++++++++ src/util/threadpool.h | 4 ++++ 3 files changed, 26 insertions(+)
Simple accessors. ACK. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

From: "Daniel P. Berrange" <berrange@redhat.com> Currently the virNetServerServicePtr is responsible for creating the virNetServerClientPtr instance when accepting a new connection. Change this so that the virNetServerServicePtr merely gives virNetServerPtr a virNetSocketPtr instance. The virNetServerPtr can then create the virNetServerClientPtr as it desires Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/libvirt_private.syms | 4 ++-- src/rpc/virnetserver.c | 14 ++++++++++++-- src/rpc/virnetserverclient.c | 4 +--- src/rpc/virnetserverservice.c | 38 +++++++++++++++----------------------- src/rpc/virnetserverservice.h | 4 +++- 5 files changed, 33 insertions(+), 31 deletions(-) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 3a23fe6..e3d31dc 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1487,8 +1487,6 @@ virNetServerKeepAliveRequired; virNetServerNew; virNetServerQuit; virNetServerRun; -virNetServerServiceNewTCP; -virNetServerServiceNewUNIX; virNetServerSetTLSContext; virNetServerUpdateServices; @@ -1553,7 +1551,9 @@ virNetServerProgramUnknownError; # virnetserverservice.h virNetServerServiceClose; virNetServerServiceGetAuth; +virNetServerServiceGetMaxRequests; virNetServerServiceGetPort; +virNetServerServiceGetTLSContext; virNetServerServiceIsReadonly; virNetServerServiceNewTCP; virNetServerServiceNewUNIX; diff --git a/src/rpc/virnetserver.c b/src/rpc/virnetserver.c index 15abb56..c3eb2e5 100644 --- a/src/rpc/virnetserver.c +++ b/src/rpc/virnetserver.c @@ -261,11 +261,12 @@ cleanup: } -static int virNetServerDispatchNewClient(virNetServerServicePtr svc ATTRIBUTE_UNUSED, - virNetServerClientPtr client, +static int virNetServerDispatchNewClient(virNetServerServicePtr svc, + virNetSocketPtr clientsock, void *opaque) { virNetServerPtr srv = opaque; + virNetServerClientPtr client = NULL; virNetServerLock(srv); @@ -276,6 +277,13 @@ static int virNetServerDispatchNewClient(virNetServerServicePtr svc ATTRIBUTE_UN goto error; } + if (!(client = virNetServerClientNew(clientsock, + virNetServerServiceGetAuth(svc), + virNetServerServiceIsReadonly(svc), + virNetServerServiceGetMaxRequests(svc), + virNetServerServiceGetTLSContext(svc)))) + goto error; + if (virNetServerClientInit(client) < 0) goto error; @@ -301,6 +309,8 @@ static int virNetServerDispatchNewClient(virNetServerServicePtr svc ATTRIBUTE_UN return 0; error: + virNetServerClientClose(client); + virObjectUnref(client); virNetServerUnlock(srv); return -1; } diff --git a/src/rpc/virnetserverclient.c b/src/rpc/virnetserverclient.c index d135b0f..9f033c8 100644 --- a/src/rpc/virnetserverclient.c +++ b/src/rpc/virnetserverclient.c @@ -357,7 +357,7 @@ virNetServerClientPtr virNetServerClientNew(virNetSocketPtr sock, return NULL; } - client->sock = sock; + client->sock = virObjectRef(sock); client->auth = auth; client->readonly = readonly; client->tlsCtxt = virObjectRef(tls); @@ -385,8 +385,6 @@ virNetServerClientPtr virNetServerClientNew(virNetSocketPtr sock, return client; error: - /* XXX ref counting is better than this */ - client->sock = NULL; /* Caller owns 'sock' upon failure */ virObjectUnref(client); return NULL; } diff --git a/src/rpc/virnetserverservice.c b/src/rpc/virnetserverservice.c index 000b5b6..eda5ef9 100644 --- a/src/rpc/virnetserverservice.c +++ b/src/rpc/virnetserverservice.c @@ -69,40 +69,21 @@ static void virNetServerServiceAccept(virNetSocketPtr sock, void *opaque) { virNetServerServicePtr svc = opaque; - virNetServerClientPtr client = NULL; virNetSocketPtr clientsock = NULL; if (virNetSocketAccept(sock, &clientsock) < 0) - goto error; + goto cleanup; if (!clientsock) /* Connection already went away */ goto cleanup; - if (!(client = virNetServerClientNew(clientsock, - svc->auth, - svc->readonly, - svc->nrequests_client_max, - svc->tls))) - goto error; - if (!svc->dispatchFunc) - goto error; - - if (svc->dispatchFunc(svc, client, svc->dispatchOpaque) < 0) - virNetServerClientClose(client); + goto cleanup; - virObjectUnref(client); + svc->dispatchFunc(svc, clientsock, svc->dispatchOpaque); cleanup: - return; - -error: - if (client) { - virNetServerClientClose(client); - virObjectUnref(client); - } else { - virObjectUnref(clientsock); - } + virObjectUnref(clientsock); } @@ -240,6 +221,17 @@ bool virNetServerServiceIsReadonly(virNetServerServicePtr svc) } +size_t virNetServerServiceGetMaxRequests(virNetServerServicePtr svc) +{ + return svc->nrequests_client_max; +} + +virNetTLSContextPtr virNetServerServiceGetTLSContext(virNetServerServicePtr svc) +{ + return svc->tls; +} + + void virNetServerServiceSetDispatcher(virNetServerServicePtr svc, virNetServerServiceDispatchFunc func, void *opaque) diff --git a/src/rpc/virnetserverservice.h b/src/rpc/virnetserverservice.h index 98fd396..cb18e2d 100644 --- a/src/rpc/virnetserverservice.h +++ b/src/rpc/virnetserverservice.h @@ -34,7 +34,7 @@ enum { }; typedef int (*virNetServerServiceDispatchFunc)(virNetServerServicePtr svc, - virNetServerClientPtr client, + virNetSocketPtr sock, void *opaque); virNetServerServicePtr virNetServerServiceNewTCP(const char *nodename, @@ -55,6 +55,8 @@ int virNetServerServiceGetPort(virNetServerServicePtr svc); int virNetServerServiceGetAuth(virNetServerServicePtr svc); bool virNetServerServiceIsReadonly(virNetServerServicePtr svc); +size_t virNetServerServiceGetMaxRequests(virNetServerServicePtr svc); +virNetTLSContextPtr virNetServerServiceGetTLSContext(virNetServerServicePtr svc); void virNetServerServiceSetDispatcher(virNetServerServicePtr svc, virNetServerServiceDispatchFunc func, -- 1.7.11.2

On 08/09/2012 09:20 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
Currently the virNetServerServicePtr is responsible for creating the virNetServerClientPtr instance when accepting a new connection. Change this so that the virNetServerServicePtr merely gives virNetServerPtr a virNetSocketPtr instance. The virNetServerPtr can then create the virNetServerClientPtr as it desires
Yes, that seems like a better division of labor.
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/libvirt_private.syms | 4 ++-- src/rpc/virnetserver.c | 14 ++++++++++++-- src/rpc/virnetserverclient.c | 4 +--- src/rpc/virnetserverservice.c | 38 +++++++++++++++----------------------- src/rpc/virnetserverservice.h | 4 +++- 5 files changed, 33 insertions(+), 31 deletions(-)
ACK. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

From: "Daniel P. Berrange" <berrange@redhat.com> Currently the virNetClientPtr constructor will always register the async IO event handler and the keepalive objects. In the case of the lock manager, there will be no event loop available not keepalive support required. Split this setup out of the constructor and into separate methods. The remote driver will enable async IO and keepalives, while the LXC driver will only even async IO Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/libvirt_private.syms | 2 ++ src/lxc/lxc_monitor.c | 2 ++ src/remote/remote_driver.c | 9 ++++++ src/rpc/virnetclient.c | 81 ++++++++++++++++++++++++++++++---------------- src/rpc/virnetclient.h | 3 ++ 5 files changed, 69 insertions(+), 28 deletions(-) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index e3d31dc..78d944f 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1320,6 +1320,8 @@ virNetClientNewExternal; virNetClientNewSSH; virNetClientNewTCP; virNetClientNewUNIX; +virNetClientRegisterASyncIO; +virNetClientRegisterKeepAlive; virNetClientRemoteAddrString; virNetClientRemoveStream; virNetClientSendNoReply; diff --git a/src/lxc/lxc_monitor.c b/src/lxc/lxc_monitor.c index a81d0f7..6378ca0 100644 --- a/src/lxc/lxc_monitor.c +++ b/src/lxc/lxc_monitor.c @@ -129,6 +129,8 @@ virLXCMonitorPtr virLXCMonitorNew(virDomainObjPtr vm, if (!(mon->client = virNetClientNewUNIX(sockpath, false, NULL))) goto error; + if (virNetClientRegisterASyncIO(mon->client) < 0) + goto error; if (!(mon->program = virNetClientProgramNew(VIR_LXC_PROTOCOL_PROGRAM, VIR_LXC_PROTOCOL_PROGRAM_VERSION, diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index 8153d70..bc6d746 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -686,6 +686,15 @@ doRemoteOpen(virConnectPtr conn, } /* switch (transport) */ + if (virNetClientRegisterASyncIO(priv->client) < 0) { + VIR_DEBUG("Failed to add event watch, disabling events and support for" + " keepalive messages"); + virResetLastError(); + } else { + if (virNetClientRegisterKeepAlive(priv->client) < 0) + goto failed; + } + virNetClientSetCloseCallback(priv->client, remoteClientCloseFunc, conn, NULL); diff --git a/src/rpc/virnetclient.c b/src/rpc/virnetclient.c index 42bdc54..f711751 100644 --- a/src/rpc/virnetclient.c +++ b/src/rpc/virnetclient.c @@ -68,6 +68,7 @@ struct _virNetClient { virMutex lock; virNetSocketPtr sock; + bool asyncIO; virNetTLSSessionPtr tls; char *hostname; @@ -309,7 +310,6 @@ static virNetClientPtr virNetClientNew(virNetSocketPtr sock, { virNetClientPtr client = NULL; int wakeupFD[2] = { -1, -1 }; - virKeepAlivePtr ka = NULL; if (virNetClientInitialize() < 0) return NULL; @@ -337,29 +337,6 @@ static virNetClientPtr virNetClientNew(virNetSocketPtr sock, !(client->hostname = strdup(hostname))) goto no_memory; - /* Set up a callback to listen on the socket data */ - virObjectRef(client); - if (virNetSocketAddIOCallback(client->sock, - VIR_EVENT_HANDLE_READABLE, - virNetClientIncomingEvent, - client, - virObjectFreeCallback) < 0) { - virObjectUnref(client); - VIR_DEBUG("Failed to add event watch, disabling events and support for" - " keepalive messages"); - } else { - /* Keepalive protocol consists of async messages so it can only be used - * if the client supports them */ - if (!(ka = virKeepAliveNew(-1, 0, client, - virNetClientKeepAliveSendCB, - virNetClientKeepAliveDeadCB, - virObjectFreeCallback))) - goto error; - /* keepalive object has a reference to client */ - virObjectRef(client); - } - - client->keepalive = ka; PROBE(RPC_CLIENT_NEW, "client=%p sock=%p", client, client->sock); @@ -370,10 +347,6 @@ no_memory: error: VIR_FORCE_CLOSE(wakeupFD[0]); VIR_FORCE_CLOSE(wakeupFD[1]); - if (ka) { - virKeepAliveStop(ka); - virObjectUnref(ka); - } virObjectUnref(client); return NULL; } @@ -433,6 +406,58 @@ virNetClientPtr virNetClientNewExternal(const char **cmdargv) } +int virNetClientRegisterASyncIO(virNetClientPtr client) +{ + if (client->asyncIO) + return 0; + + /* Set up a callback to listen on the socket data */ + virObjectRef(client); + if (virNetSocketAddIOCallback(client->sock, + VIR_EVENT_HANDLE_READABLE, + virNetClientIncomingEvent, + client, + virObjectFreeCallback) < 0) { + virObjectUnref(client); + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Unable to register async IO callback")); + return -1; + } + + client->asyncIO = true; + return 0; +} + + +int virNetClientRegisterKeepAlive(virNetClientPtr client) +{ + virKeepAlivePtr ka; + + if (client->keepalive) + return 0; + + if (!client->asyncIO) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("Unable to enable keepalives without async IO support")); + return -1; + } + + /* Keepalive protocol consists of async messages so it can only be used + * if the client supports them */ + if (!(ka = virKeepAliveNew(-1, 0, client, + virNetClientKeepAliveSendCB, + virNetClientKeepAliveDeadCB, + virObjectFreeCallback))) + return -1; + + /* keepalive object has a reference to client */ + virObjectRef(client); + + client->keepalive = ka; + return 0; +} + + int virNetClientGetFD(virNetClientPtr client) { int fd; diff --git a/src/rpc/virnetclient.h b/src/rpc/virnetclient.h index d6b9b3c..70da794 100644 --- a/src/rpc/virnetclient.h +++ b/src/rpc/virnetclient.h @@ -52,6 +52,9 @@ virNetClientPtr virNetClientNewSSH(const char *nodename, virNetClientPtr virNetClientNewExternal(const char **cmdargv); +int virNetClientRegisterASyncIO(virNetClientPtr client); +int virNetClientRegisterKeepAlive(virNetClientPtr client); + typedef void (*virNetClientCloseFunc)(virNetClientPtr client, int reason, void *opaque); -- 1.7.11.2

On 08/09/2012 09:20 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
Currently the virNetClientPtr constructor will always register the async IO event handler and the keepalive objects. In the case of the lock manager, there will be no event loop available not keepalive support required. Split this setup out of the
s/not/nor/
constructor and into separate methods.
The remote driver will enable async IO and keepalives, while the LXC driver will only even async IO
s/even/enable/
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/libvirt_private.syms | 2 ++ src/lxc/lxc_monitor.c | 2 ++ src/remote/remote_driver.c | 9 ++++++ src/rpc/virnetclient.c | 81 ++++++++++++++++++++++++++++++---------------- src/rpc/virnetclient.h | 3 ++ 5 files changed, 69 insertions(+), 28 deletions(-)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index e3d31dc..78d944f 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1320,6 +1320,8 @@ virNetClientNewExternal; virNetClientNewSSH; virNetClientNewTCP; virNetClientNewUNIX; +virNetClientRegisterASyncIO;
We have 236 instances of 'Async' in the code base, and 0 instances of 'ASync' prior to this. I thin you want s/ASync/Async/g throughout the patch. ACK with that change. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

From: "Daniel P. Berrange" <berrange@redhat.com> Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/locking/domain_lock.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/locking/domain_lock.c b/src/locking/domain_lock.c index ba49f9c..4ac16f2 100644 --- a/src/locking/domain_lock.c +++ b/src/locking/domain_lock.c @@ -160,6 +160,9 @@ int virDomainLockProcessStart(virLockManagerPluginPtr plugin, int ret; int flags = VIR_LOCK_MANAGER_ACQUIRE_RESTRICT; + VIR_DEBUG("plugin=%p dom=%p paused=%d fd=%p", + plugin, dom, paused, fd); + if (!(lock = virDomainLockManagerNew(plugin, dom, true))) return -1; @@ -180,6 +183,9 @@ int virDomainLockProcessPause(virLockManagerPluginPtr plugin, virLockManagerPtr lock; int ret; + VIR_DEBUG("plugin=%p dom=%p state=%p", + plugin, dom, state); + if (!(lock = virDomainLockManagerNew(plugin, dom, true))) return -1; @@ -196,6 +202,9 @@ int virDomainLockProcessResume(virLockManagerPluginPtr plugin, virLockManagerPtr lock; int ret; + VIR_DEBUG("plugin=%p dom=%p state=%s", + plugin, dom, NULLSTR(state)); + if (!(lock = virDomainLockManagerNew(plugin, dom, true))) return -1; @@ -212,6 +221,9 @@ int virDomainLockProcessInquire(virLockManagerPluginPtr plugin, virLockManagerPtr lock; int ret; + VIR_DEBUG("plugin=%p dom=%p state=%p", + plugin, dom, state); + if (!(lock = virDomainLockManagerNew(plugin, dom, true))) return -1; @@ -229,6 +241,9 @@ int virDomainLockDiskAttach(virLockManagerPluginPtr plugin, virLockManagerPtr lock; int ret = -1; + VIR_DEBUG("plugin=%p dom=%p disk=%p", + plugin, dom, disk); + if (!(lock = virDomainLockManagerNew(plugin, dom, false))) return -1; @@ -253,6 +268,9 @@ int virDomainLockDiskDetach(virLockManagerPluginPtr plugin, virLockManagerPtr lock; int ret = -1; + VIR_DEBUG("plugin=%p dom=%p disk=%p", + plugin, dom, disk); + if (!(lock = virDomainLockManagerNew(plugin, dom, false))) return -1; @@ -278,6 +296,9 @@ int virDomainLockLeaseAttach(virLockManagerPluginPtr plugin, virLockManagerPtr lock; int ret = -1; + VIR_DEBUG("plugin=%p dom=%p lease=%p", + plugin, dom, lease); + if (!(lock = virDomainLockManagerNew(plugin, dom, false))) return -1; @@ -302,6 +323,9 @@ int virDomainLockLeaseDetach(virLockManagerPluginPtr plugin, virLockManagerPtr lock; int ret = -1; + VIR_DEBUG("plugin=%p dom=%p lease=%p", + plugin, dom, lease); + if (!(lock = virDomainLockManagerNew(plugin, dom, false))) return -1; -- 1.7.11.2

On 08/09/2012 09:20 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/locking/domain_lock.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+)
No functional change, but easier to debug. ACK.
diff --git a/src/locking/domain_lock.c b/src/locking/domain_lock.c index ba49f9c..4ac16f2 100644 --- a/src/locking/domain_lock.c +++ b/src/locking/domain_lock.c @@ -160,6 +160,9 @@ int virDomainLockProcessStart(virLockManagerPluginPtr plugin, int ret; int flags = VIR_LOCK_MANAGER_ACQUIRE_RESTRICT;
+ VIR_DEBUG("plugin=%p dom=%p paused=%d fd=%p", + plugin, dom, paused, fd);
I did a double-take on the fd=%p, but it really is a pointer whose contents will be set later on, and not an int whose value we report now. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

From: "Daniel P. Berrange" <berrange@redhat.com> Currently there is a hook function that is invoked when a new client connection comes in, which allows an app to setup private data. This setup will make it difficult to serialize client state during process re-exec(). Change to a model where the app registers a callback when creating the virNetServerPtr instance, which is used to allocate the client private data immediately during virNetClientPtr construction. Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- daemon/libvirtd.c | 1 + daemon/remote.c | 15 ++++++--------- daemon/remote.h | 6 +++--- src/lxc/lxc_controller.c | 23 +++++++++++++++++------ src/rpc/virnetserver.c | 24 +++++++++++++----------- src/rpc/virnetserver.h | 9 +++------ src/rpc/virnetserverclient.c | 31 +++++++++++++------------------ src/rpc/virnetserverclient.h | 13 +++++++------ 8 files changed, 63 insertions(+), 59 deletions(-) diff --git a/daemon/libvirtd.c b/daemon/libvirtd.c index fa9e7e8..24e20f8 100644 --- a/daemon/libvirtd.c +++ b/daemon/libvirtd.c @@ -1196,6 +1196,7 @@ int main(int argc, char **argv) { !!config->keepalive_required, config->mdns_adv ? config->mdns_name : NULL, remoteClientInitHook, + remoteClientFreeFunc, NULL))) { ret = VIR_DAEMON_ERR_INIT; goto cleanup; diff --git a/daemon/remote.c b/daemon/remote.c index 832307e..851bcc1 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -631,7 +631,7 @@ verify(ARRAY_CARDINALITY(domainEventCallbacks) == VIR_DOMAIN_EVENT_ID_LAST); * We keep the libvirt connection open until any async * jobs have finished, then clean it up elsewhere */ -static void remoteClientFreeFunc(void *data) +void remoteClientFreeFunc(void *data) { struct daemonClientPrivate *priv = data; @@ -663,31 +663,28 @@ static void remoteClientCloseFunc(virNetServerClientPtr client) } -int remoteClientInitHook(virNetServerPtr srv ATTRIBUTE_UNUSED, - virNetServerClientPtr client, - void *opaque ATTRIBUTE_UNUSED) +void *remoteClientInitHook(virNetServerClientPtr client, + void *opaque ATTRIBUTE_UNUSED) { struct daemonClientPrivate *priv; int i; if (VIR_ALLOC(priv) < 0) { virReportOOMError(); - return -1; + return NULL; } if (virMutexInit(&priv->lock) < 0) { VIR_FREE(priv); virReportOOMError(); - return -1; + return NULL; } for (i = 0 ; i < VIR_DOMAIN_EVENT_ID_LAST ; i++) priv->domainEventCallbackID[i] = -1; - virNetServerClientSetPrivateData(client, priv, - remoteClientFreeFunc); virNetServerClientSetCloseHook(client, remoteClientCloseFunc); - return 0; + return priv; } /*----- Functions. -----*/ diff --git a/daemon/remote.h b/daemon/remote.h index 9f662cb..2806494 100644 --- a/daemon/remote.h +++ b/daemon/remote.h @@ -35,8 +35,8 @@ extern size_t remoteNProcs; extern virNetServerProgramProc qemuProcs[]; extern size_t qemuNProcs; -int remoteClientInitHook(virNetServerPtr srv, - virNetServerClientPtr client, - void *opaque); +void remoteClientFreeFunc(void *data); +void *remoteClientInitHook(virNetServerClientPtr client, + void *opaque); #endif /* __LIBVIRTD_REMOTE_H__ */ diff --git a/src/lxc/lxc_controller.c b/src/lxc/lxc_controller.c index cb9fa41..4c3c17f 100644 --- a/src/lxc/lxc_controller.c +++ b/src/lxc/lxc_controller.c @@ -578,16 +578,26 @@ static void virLXCControllerClientCloseHook(virNetServerClientPtr client) } } -static int virLXCControllerClientHook(virNetServerPtr server ATTRIBUTE_UNUSED, - virNetServerClientPtr client, - void *opaque) +static void virLXCControllerClientPrivateFree(void *data) +{ + VIR_FREE(data); +} + +static void *virLXCControllerClientPrivateNew(virNetServerClientPtr client, + void *opaque) { virLXCControllerPtr ctrl = opaque; - virNetServerClientSetPrivateData(client, ctrl, NULL); + int *dummy; + + if (VIR_ALLOC(dummy) < 0) { + virReportOOMError(); + return NULL; + } + virNetServerClientSetCloseHook(client, virLXCControllerClientCloseHook); VIR_DEBUG("Got new client %p", client); ctrl->client = client; - return 0; + return dummy; } @@ -605,7 +615,8 @@ static int virLXCControllerSetupServer(virLXCControllerPtr ctrl) if (!(ctrl->server = virNetServerNew(0, 0, 0, 1, -1, 0, false, NULL, - virLXCControllerClientHook, + virLXCControllerClientPrivateNew, + virLXCControllerClientPrivateFree, ctrl))) goto error; diff --git a/src/rpc/virnetserver.c b/src/rpc/virnetserver.c index c3eb2e5..03cf0b7 100644 --- a/src/rpc/virnetserver.c +++ b/src/rpc/virnetserver.c @@ -104,8 +104,9 @@ struct _virNetServer { virNetServerAutoShutdownFunc autoShutdownFunc; void *autoShutdownOpaque; - virNetServerClientInitHook clientInitHook; - void *clientInitOpaque; + virNetServerClientPrivNew clientPrivNew; + virFreeCallback clientPrivFree; + void *clientPrivOpaque; }; @@ -281,16 +282,15 @@ static int virNetServerDispatchNewClient(virNetServerServicePtr svc, virNetServerServiceGetAuth(svc), virNetServerServiceIsReadonly(svc), virNetServerServiceGetMaxRequests(svc), - virNetServerServiceGetTLSContext(svc)))) + virNetServerServiceGetTLSContext(svc), + srv->clientPrivNew, + srv->clientPrivFree, + srv->clientPrivOpaque))) goto error; if (virNetServerClientInit(client) < 0) goto error; - if (srv->clientInitHook && - srv->clientInitHook(srv, client, srv->clientInitOpaque) < 0) - goto error; - if (VIR_EXPAND_N(srv->clients, srv->nclients, 1) < 0) { virReportOOMError(); goto error; @@ -352,8 +352,9 @@ virNetServerPtr virNetServerNew(size_t min_workers, unsigned int keepaliveCount, bool keepaliveRequired, const char *mdnsGroupName, - virNetServerClientInitHook clientInitHook, - void *opaque) + virNetServerClientPrivNew clientPrivNew, + virFreeCallback clientPrivFree, + void *clientPrivOpaque) { virNetServerPtr srv; struct sigaction sig_action; @@ -376,8 +377,9 @@ virNetServerPtr virNetServerNew(size_t min_workers, srv->keepaliveCount = keepaliveCount; srv->keepaliveRequired = keepaliveRequired; srv->sigwrite = srv->sigread = -1; - srv->clientInitHook = clientInitHook; - srv->clientInitOpaque = opaque; + srv->clientPrivNew = clientPrivNew; + srv->clientPrivFree = clientPrivFree; + srv->clientPrivOpaque = clientPrivOpaque; srv->privileged = geteuid() == 0 ? true : false; if (mdnsGroupName && diff --git a/src/rpc/virnetserver.h b/src/rpc/virnetserver.h index 7dc52ca..9188072 100644 --- a/src/rpc/virnetserver.h +++ b/src/rpc/virnetserver.h @@ -32,10 +32,6 @@ # include "virnetserverservice.h" # include "virobject.h" -typedef int (*virNetServerClientInitHook)(virNetServerPtr srv, - virNetServerClientPtr client, - void *opaque); - virNetServerPtr virNetServerNew(size_t min_workers, size_t max_workers, size_t priority_workers, @@ -44,8 +40,9 @@ virNetServerPtr virNetServerNew(size_t min_workers, unsigned int keepaliveCount, bool keepaliveRequired, const char *mdnsGroupName, - virNetServerClientInitHook clientInitHook, - void *opaque); + virNetServerClientPrivNew clientPrivNew, + virFreeCallback clientPrivFree, + void *clientPrivOpaque); typedef int (*virNetServerAutoShutdownFunc)(virNetServerPtr srv, void *opaque); diff --git a/src/rpc/virnetserverclient.c b/src/rpc/virnetserverclient.c index 9f033c8..c9703fd 100644 --- a/src/rpc/virnetserverclient.c +++ b/src/rpc/virnetserverclient.c @@ -97,7 +97,7 @@ struct _virNetServerClient void *dispatchOpaque; void *privateData; - virNetServerClientFreeFunc privateDataFreeFunc; + virFreeCallback privateDataFreeFunc; virNetServerClientCloseFunc privateDataCloseFunc; virKeepAlivePtr keepalive; @@ -340,7 +340,10 @@ virNetServerClientPtr virNetServerClientNew(virNetSocketPtr sock, int auth, bool readonly, size_t nrequests_max, - virNetTLSContextPtr tls) + virNetTLSContextPtr tls, + virNetServerClientPrivNew privNew, + virFreeCallback privFree, + void *privOpaque) { virNetServerClientPtr client; @@ -378,6 +381,14 @@ virNetServerClientPtr virNetServerClientNew(virNetSocketPtr sock, } client->nrequests = 1; + if (privNew) { + if (!(client->privateData = privNew(client, privOpaque))) { + virObjectUnref(client); + goto error; + } + client->privateDataFreeFunc = privFree; + } + PROBE(RPC_SERVER_CLIENT_NEW, "client=%p sock=%p", client, client->sock); @@ -507,22 +518,6 @@ const char *virNetServerClientGetIdentity(virNetServerClientPtr client) return identity; } -void virNetServerClientSetPrivateData(virNetServerClientPtr client, - void *opaque, - virNetServerClientFreeFunc ff) -{ - virNetServerClientLock(client); - - if (client->privateData && - client->privateDataFreeFunc) - client->privateDataFreeFunc(client->privateData); - - client->privateData = opaque; - client->privateDataFreeFunc = ff; - - virNetServerClientUnlock(client); -} - void *virNetServerClientGetPrivateData(virNetServerClientPtr client) { diff --git a/src/rpc/virnetserverclient.h b/src/rpc/virnetserverclient.h index a1ff19b..f950c61 100644 --- a/src/rpc/virnetserverclient.h +++ b/src/rpc/virnetserverclient.h @@ -39,11 +39,17 @@ typedef int (*virNetServerClientFilterFunc)(virNetServerClientPtr client, virNetMessagePtr msg, void *opaque); +typedef void *(*virNetServerClientPrivNew)(virNetServerClientPtr client, + void *opaque); + virNetServerClientPtr virNetServerClientNew(virNetSocketPtr sock, int auth, bool readonly, size_t nrequests_max, - virNetTLSContextPtr tls); + virNetTLSContextPtr tls, + virNetServerClientPrivNew privNew, + virFreeCallback privFree, + void *privOpaque); int virNetServerClientAddFilter(virNetServerClientPtr client, virNetServerClientFilterFunc func, @@ -74,11 +80,6 @@ const char *virNetServerClientGetIdentity(virNetServerClientPtr client); int virNetServerClientGetUNIXIdentity(virNetServerClientPtr client, uid_t *uid, gid_t *gid, pid_t *pid); -typedef void (*virNetServerClientFreeFunc)(void *data); - -void virNetServerClientSetPrivateData(virNetServerClientPtr client, - void *opaque, - virNetServerClientFreeFunc ff); void *virNetServerClientGetPrivateData(virNetServerClientPtr client); typedef void (*virNetServerClientCloseFunc)(virNetServerClientPtr client); -- 1.7.11.2

On 08/09/2012 09:20 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
Currently there is a hook function that is invoked when a new client connection comes in, which allows an app to setup private data. This setup will make it difficult to serialize client state during process re-exec(). Change to a model where the app registers a callback when creating the virNetServerPtr instance, which is used to allocate the client private data immediately during virNetClientPtr construction.
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- daemon/libvirtd.c | 1 + daemon/remote.c | 15 ++++++--------- daemon/remote.h | 6 +++--- src/lxc/lxc_controller.c | 23 +++++++++++++++++------ src/rpc/virnetserver.c | 24 +++++++++++++----------- src/rpc/virnetserver.h | 9 +++------ src/rpc/virnetserverclient.c | 31 +++++++++++++------------------ src/rpc/virnetserverclient.h | 13 +++++++------ 8 files changed, 63 insertions(+), 59 deletions(-)
Fails 'make check': GEN check-symfile Expected symbol virNetServerClientSetPrivateData is not in ELF library
+++ b/src/lxc/lxc_controller.c +static void *virLXCControllerClientPrivateNew(virNetServerClientPtr client, + void *opaque) { virLXCControllerPtr ctrl = opaque; - virNetServerClientSetPrivateData(client, ctrl, NULL); + int *dummy; + + if (VIR_ALLOC(dummy) < 0) { + virReportOOMError(); + return NULL; + }
Maybe use strdup("") instead of dummy as your magic allocation? Doesn't matter at the end of the day, though. And if you never store into the allocated storage, do you even need to allocate something, or could you return ((void*)1) and have the free function be a no-op instead of a VIR_FREE, since the only reason you have to return non-NULL is to indicate success? ACK with this squashed in: diff --git i/src/libvirt_private.syms w/src/libvirt_private.syms index 75fd7bf..24915e2 100644 --- i/src/libvirt_private.syms +++ w/src/libvirt_private.syms @@ -1524,7 +1524,6 @@ virNetServerClientSendMessage; virNetServerClientSetCloseHook; virNetServerClientSetDispatcher; virNetServerClientSetIdentity; -virNetServerClientSetPrivateData; virNetServerClientStartKeepAlive; virNetServerClientWantClose; -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

From: "Daniel P. Berrange" <berrange@redhat.com> Currently the virNetServerDispatchNewClient both creates the virNetServerClientPtr instance and registers it with the virNetServerPtr internal state. Split the client registration code out into a separate virNetServerAddClient method to allow future reuse from other contexts Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/rpc/virnetserver.c | 46 ++++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/src/rpc/virnetserver.c b/src/rpc/virnetserver.c index 03cf0b7..0a6ecdc 100644 --- a/src/rpc/virnetserver.c +++ b/src/rpc/virnetserver.c @@ -262,13 +262,9 @@ cleanup: } -static int virNetServerDispatchNewClient(virNetServerServicePtr svc, - virNetSocketPtr clientsock, - void *opaque) +static int virNetServerAddClient(virNetServerPtr srv, + virNetServerClientPtr client) { - virNetServerPtr srv = opaque; - virNetServerClientPtr client = NULL; - virNetServerLock(srv); if (srv->nclients >= srv->nclients_max) { @@ -278,16 +274,6 @@ static int virNetServerDispatchNewClient(virNetServerServicePtr svc, goto error; } - if (!(client = virNetServerClientNew(clientsock, - virNetServerServiceGetAuth(svc), - virNetServerServiceIsReadonly(svc), - virNetServerServiceGetMaxRequests(svc), - virNetServerServiceGetTLSContext(svc), - srv->clientPrivNew, - srv->clientPrivFree, - srv->clientPrivOpaque))) - goto error; - if (virNetServerClientInit(client) < 0) goto error; @@ -309,12 +295,36 @@ static int virNetServerDispatchNewClient(virNetServerServicePtr svc, return 0; error: - virNetServerClientClose(client); - virObjectUnref(client); virNetServerUnlock(srv); return -1; } +static int virNetServerDispatchNewClient(virNetServerServicePtr svc, + virNetSocketPtr clientsock, + void *opaque) +{ + virNetServerPtr srv = opaque; + virNetServerClientPtr client; + + if (!(client = virNetServerClientNew(clientsock, + virNetServerServiceGetAuth(svc), + virNetServerServiceIsReadonly(svc), + virNetServerServiceGetMaxRequests(svc), + virNetServerServiceGetTLSContext(svc), + srv->clientPrivNew, + srv->clientPrivFree, + srv->clientPrivOpaque))) + return -1; + + if (virNetServerAddClient(srv, client) < 0) { + virNetServerClientClose(client); + virObjectUnref(client); + return -1; + } + virObjectUnref(client); + return 0; +} + static void virNetServerFatalSignal(int sig, siginfo_t *siginfo ATTRIBUTE_UNUSED, -- 1.7.11.2

On 08/09/2012 09:20 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
Currently the virNetServerDispatchNewClient both creates the virNetServerClientPtr instance and registers it with the virNetServerPtr internal state. Split the client registration code out into a separate virNetServerAddClient method to allow future reuse from other contexts
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/rpc/virnetserver.c | 46 ++++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-)
ACK. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

From: "Daniel P. Berrange" <berrange@redhat.com> In preparation for adding further constructors, refactor the virNetServerClientNew method to move most of the code into a common virNetServerClientNewInternal helper API. Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/rpc/virnetserverclient.c | 52 +++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/src/rpc/virnetserverclient.c b/src/rpc/virnetserverclient.c index c9703fd..acd2b4d 100644 --- a/src/rpc/virnetserverclient.c +++ b/src/rpc/virnetserverclient.c @@ -336,19 +336,15 @@ static void virNetServerClientSockTimerFunc(int timer, } -virNetServerClientPtr virNetServerClientNew(virNetSocketPtr sock, - int auth, - bool readonly, - size_t nrequests_max, - virNetTLSContextPtr tls, - virNetServerClientPrivNew privNew, - virFreeCallback privFree, - void *privOpaque) +static virNetServerClientPtr +virNetServerClientNewInternal(virNetSocketPtr sock, + int auth, + bool readonly, + size_t nrequests_max, + virNetTLSContextPtr tls) { virNetServerClientPtr client; - VIR_DEBUG("sock=%p auth=%d tls=%p", sock, auth, tls); - if (virNetServerClientInitialize() < 0) return NULL; @@ -381,14 +377,6 @@ virNetServerClientPtr virNetServerClientNew(virNetSocketPtr sock, } client->nrequests = 1; - if (privNew) { - if (!(client->privateData = privNew(client, privOpaque))) { - virObjectUnref(client); - goto error; - } - client->privateDataFreeFunc = privFree; - } - PROBE(RPC_SERVER_CLIENT_NEW, "client=%p sock=%p", client, client->sock); @@ -401,6 +389,34 @@ error: } +virNetServerClientPtr virNetServerClientNew(virNetSocketPtr sock, + int auth, + bool readonly, + size_t nrequests_max, + virNetTLSContextPtr tls, + virNetServerClientPrivNew privNew, + virFreeCallback privFree, + void *privOpaque) +{ + virNetServerClientPtr client; + + VIR_DEBUG("sock=%p auth=%d tls=%p", sock, auth, tls); + + if (!(client = virNetServerClientNewInternal(sock, auth, readonly, nrequests_max, tls))) + return NULL; + + if (privNew) { + if (!(client->privateData = privNew(client, privOpaque))) { + virObjectUnref(client); + return NULL; + } + client->privateDataFreeFunc = privFree; + } + + return client; +} + + int virNetServerClientGetAuth(virNetServerClientPtr client) { int auth; -- 1.7.11.2

On 08/09/2012 09:20 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
In preparation for adding further constructors, refactor the virNetServerClientNew method to move most of the code into a common virNetServerClientNewInternal helper API.
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/rpc/virnetserverclient.c | 52 +++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 18 deletions(-)
ACK. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

From: "Daniel P. Berrange" <berrange@redhat.com> In order to support systemd socket based activation, it needs to be possible to create virNetSocketPtr and virNetServerServicePtr instance from a pre-opened file descriptor --- src/libvirt_private.syms | 2 ++ src/rpc/virnetserverservice.c | 49 +++++++++++++++++++++++++++++++++++++++++++ src/rpc/virnetserverservice.h | 5 +++++ src/rpc/virnetsocket.c | 20 ++++++++++++++++++ src/rpc/virnetsocket.h | 3 +++ 5 files changed, 79 insertions(+) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 78d944f..a0db0e5 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1557,6 +1557,7 @@ virNetServerServiceGetMaxRequests; virNetServerServiceGetPort; virNetServerServiceGetTLSContext; virNetServerServiceIsReadonly; +virNetServerServiceNewFD; virNetServerServiceNewTCP; virNetServerServiceNewUNIX; virNetServerServiceSetDispatcher; @@ -1582,6 +1583,7 @@ virNetSocketNewConnectExternal; virNetSocketNewConnectSSH; virNetSocketNewConnectTCP; virNetSocketNewConnectUNIX; +virNetSocketNewListenFD; virNetSocketNewListenTCP; virNetSocketNewListenUNIX; virNetSocketRead; diff --git a/src/rpc/virnetserverservice.c b/src/rpc/virnetserverservice.c index eda5ef9..53ff503 100644 --- a/src/rpc/virnetserverservice.c +++ b/src/rpc/virnetserverservice.c @@ -200,6 +200,55 @@ error: return NULL; } +virNetServerServicePtr virNetServerServiceNewFD(int fd, + int auth, + bool readonly, + size_t nrequests_client_max, + virNetTLSContextPtr tls) +{ + virNetServerServicePtr svc; + int i; + + if (virNetServerServiceInitialize() < 0) + return NULL; + + if (!(svc = virObjectNew(virNetServerServiceClass))) + return NULL; + + svc->auth = auth; + svc->readonly = readonly; + svc->nrequests_client_max = nrequests_client_max; + svc->tls = virObjectRef(tls); + + svc->nsocks = 1; + if (VIR_ALLOC_N(svc->socks, svc->nsocks) < 0) + goto no_memory; + + if (virNetSocketNewListenFD(fd, + &svc->socks[0]) < 0) + goto error; + + for (i = 0 ; i < svc->nsocks ; i++) { + /* IO callback is initially disabled, until we're ready + * to deal with incoming clients */ + if (virNetSocketAddIOCallback(svc->socks[i], + 0, + virNetServerServiceAccept, + svc, + virObjectFreeCallback) < 0) + goto error; + } + + + return svc; + +no_memory: + virReportOOMError(); +error: + virObjectUnref(svc); + return NULL; +} + int virNetServerServiceGetPort(virNetServerServicePtr svc) { diff --git a/src/rpc/virnetserverservice.h b/src/rpc/virnetserverservice.h index cb18e2d..48f49e7 100644 --- a/src/rpc/virnetserverservice.h +++ b/src/rpc/virnetserverservice.h @@ -50,6 +50,11 @@ virNetServerServicePtr virNetServerServiceNewUNIX(const char *path, bool readonly, size_t nrequests_client_max, virNetTLSContextPtr tls); +virNetServerServicePtr virNetServerServiceNewFD(int fd, + int auth, + bool readonly, + size_t nrequests_client_max, + virNetTLSContextPtr tls); int virNetServerServiceGetPort(virNetServerServicePtr svc); diff --git a/src/rpc/virnetsocket.c b/src/rpc/virnetsocket.c index b6f156b..69c25bd 100644 --- a/src/rpc/virnetsocket.c +++ b/src/rpc/virnetsocket.c @@ -399,6 +399,26 @@ int virNetSocketNewListenUNIX(const char *path ATTRIBUTE_UNUSED, } #endif +int virNetSocketNewListenFD(int fd, + virNetSocketPtr *retsock) +{ + virSocketAddr addr; + *retsock = NULL; + + memset(&addr, 0, sizeof(addr)); + + addr.len = sizeof(addr.data); + if (getsockname(fd, &addr.data.sa, &addr.len) < 0) { + virReportSystemError(errno, "%s", _("Unable to get local socket name")); + return -1; + } + + if (!(*retsock = virNetSocketNew(&addr, NULL, false, fd, -1, 0))) + return -1; + + return 0; +} + int virNetSocketNewConnectTCP(const char *nodename, const char *service, diff --git a/src/rpc/virnetsocket.h b/src/rpc/virnetsocket.h index cc3f912..3750955 100644 --- a/src/rpc/virnetsocket.h +++ b/src/rpc/virnetsocket.h @@ -52,6 +52,9 @@ int virNetSocketNewListenUNIX(const char *path, gid_t grp, virNetSocketPtr *addr); +int virNetSocketNewListenFD(int fd, + virNetSocketPtr *addr); + int virNetSocketNewConnectTCP(const char *nodename, const char *service, virNetSocketPtr *addr); -- 1.7.11.2

On 08/09/2012 09:20 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
In order to support systemd socket based activation, it needs to be possible to create virNetSocketPtr and virNetServerServicePtr instance from a pre-opened file descriptor --- src/libvirt_private.syms | 2 ++ src/rpc/virnetserverservice.c | 49 +++++++++++++++++++++++++++++++++++++++++++ src/rpc/virnetserverservice.h | 5 +++++ src/rpc/virnetsocket.c | 20 ++++++++++++++++++ src/rpc/virnetsocket.h | 3 +++ 5 files changed, 79 insertions(+)
ACK. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

From: "Daniel P. Berrange" <berrange@redhat.com> The previously introduced virFile{Lock,Unlock} APIs provide a way to acquire/release fcntl() locks on individual files. For unknown reason though, the POSIX spec says that fcntl() locks are released when *any* file handle refering to the same path is closed. In the following sequence threadA: fd1 = open("foo") threadB: fd2 = open("foo") threadA: virFileLock(fd1) threadB: virFileLock(fd2) threadB: close(fd2) you'd expect threadA to come out holding a lock on 'foo', and indeed it does hold a lock for a very short time. Unfortunately when threadB does close(fd2) this releases the lock associated with fd1. For the current libvirt use case for virFileLock - pidfiles - this doesn't matter since the lock is acquired at startup while single threaded an never released until exit. To provide a more generally useful API though, it is neccessary to introduce a slightly higher level abstraction, which is to be referred to as a "lockspace". This is to be provided by a virLockSpacePtr object in src/util/virlockspace.{c,h}. The core idea is that the lockspace keeps track of what files are already open+locked. This means that when a 2nd thread comes along and tries to acquire a lock, it doesn't end up opening and closing a new FD. The lockspace just checks the current list of held locks and immediately returns VIR_ERR_RESOURCE_BUSY. NB, the API as it stands is designed on the basis that the files being locked are not being otherwise opened and used by the application code. One approach to using this API is to acquire locks based on a hash of the filepath. eg to lock /var/lib/libvirt/images/foo.img the application might do virLockSpacePtr lockspace = virLockSpaceNew("/var/lib/libvirt/imagelocks"); lockname = md5sum("/var/lib/libvirt/images/foo.img") virLockSpaceAcquireLock(lockspace, lockname) It is also possible to do locks directly on resources by using a NULL lockspace directory and then using the file path as the lock name eg virLockSpacePtr lockspace = virLockSpaceNew(NULL) virLockSpaceAcquireLock(lockspace, "/var/lib/libvirt/images/foo.img") This is only safe todo though if no other part of the proces will be opening the files. This will be the case when this code is used inside the soon-to-be-reposted virlockd daemon Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- .gitignore | 1 + include/libvirt/virterror.h | 2 + po/POTFILES.in | 1 + src/Makefile.am | 1 + src/libvirt_private.syms | 11 + src/util/virlockspace.c | 548 ++++++++++++++++++++++++++++++++++++++++++++ src/util/virlockspace.h | 58 +++++ src/util/virterror.c | 9 +- tests/Makefile.am | 7 +- tests/virlockspacetest.c | 363 +++++++++++++++++++++++++++++ 10 files changed, 999 insertions(+), 2 deletions(-) create mode 100644 src/util/virlockspace.c create mode 100644 src/util/virlockspace.h create mode 100644 tests/virlockspacetest.c diff --git a/.gitignore b/.gitignore index cd8a85c..37767e0 100644 --- a/.gitignore +++ b/.gitignore @@ -158,6 +158,7 @@ /tests/virdrivermoduletest /tests/virhashtest /tests/virkeyfiletest +/tests/virlockspacetest /tests/virnet*test /tests/virshtest /tests/virtimetest diff --git a/include/libvirt/virterror.h b/include/libvirt/virterror.h index ad8e101..3e72cb7 100644 --- a/include/libvirt/virterror.h +++ b/include/libvirt/virterror.h @@ -110,6 +110,7 @@ typedef enum { VIR_FROM_AUTH = 46, /* Error from auth handling */ VIR_FROM_DBUS = 47, /* Error from DBus */ VIR_FROM_PARALLELS = 48, /* Error from Parallels */ + VIR_FROM_LOCKSPACE = 49, /* Error from lockspace */ # ifdef VIR_ENUM_SENTINELS VIR_ERR_DOMAIN_LAST @@ -277,6 +278,7 @@ typedef enum { VIR_ERR_MIGRATE_UNSAFE = 81, /* Migration is not safe */ VIR_ERR_OVERFLOW = 82, /* integer overflow */ VIR_ERR_BLOCK_COPY_ACTIVE = 83, /* action prevented by block copy job */ + VIR_ERR_RESOURCE_BUSY = 84, /* resource is already in use */ } virErrorNumber; /** diff --git a/po/POTFILES.in b/po/POTFILES.in index 37a00ee..183ab42 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -143,6 +143,7 @@ src/util/virdbus.c src/util/virfile.c src/util/virhash.c src/util/virkeyfile.c +src/util/virlockspace.c src/util/virnetdev.c src/util/virnetdevbridge.c src/util/virnetdevmacvlan.c diff --git a/src/Makefile.am b/src/Makefile.am index 6ed4a41..81ed279 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -96,6 +96,7 @@ UTIL_SOURCES = \ util/virkeycode.c util/virkeycode.h \ util/virkeyfile.c util/virkeyfile.h \ util/virkeymaps.h \ + util/virlockspace.c util/virlockspace.h \ util/virmacaddr.h util/virmacaddr.c \ util/virnetdev.h util/virnetdev.c \ util/virnetdevbandwidth.h util/virnetdevbandwidth.c \ diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index a0db0e5..d05c246 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1288,6 +1288,17 @@ virKeyFileHasGroup; virKeyFileGetValueString; +# virlockspace.h +virLockSpaceAcquireResource; +virLockSpaceCreateResource; +virLockSpaceDeleteResource; +virLockSpaceFree; +virLockSpaceGetDirectory; +virLockSpaceNew; +virLockSpaceReleaseResource; +virLockSpaceReleaseResourcesForOwner; + + # virmacaddr.h virMacAddrCmp; virMacAddrCmpRaw; diff --git a/src/util/virlockspace.c b/src/util/virlockspace.c new file mode 100644 index 0000000..47dde66 --- /dev/null +++ b/src/util/virlockspace.c @@ -0,0 +1,548 @@ +/* + * virlockspace.c: simple file based lockspaces + * + * Copyright (C) 2012 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 "virlockspace.h" +#include "logging.h" +#include "memory.h" +#include "virterror_internal.h" +#include "util.h" +#include "virfile.h" +#include "virhash.h" +#include "threads.h" + +#include <fcntl.h> +#include <unistd.h> +#include <sys/stat.h> + +#define VIR_FROM_THIS VIR_FROM_LOCKSPACE + +typedef struct _virLockSpaceResource virLockSpaceResource; +typedef virLockSpaceResource *virLockSpaceResourcePtr; + +struct _virLockSpaceResource { + char *name; + char *path; + int fd; + bool lockHeld; + unsigned int flags; + size_t nOwners; + pid_t *owners; +}; + +struct _virLockSpace { + char *dir; + virMutex lock; + + virHashTablePtr resources; +}; + + +static char *virLockSpaceGetResourcePath(virLockSpacePtr lockspace, + const char *resname) +{ + char *ret; + if (lockspace->dir) { + if (virAsprintf(&ret, "%s/%s", lockspace->dir, resname) < 0) { + virReportOOMError(); + return NULL; + } + } else { + if (!(ret = strdup(resname))) { + virReportOOMError(); + return NULL; + } + } + + return ret; +} + + +static void virLockSpaceResourceFree(virLockSpaceResourcePtr res) +{ + if (!res) + return; + + if (res->lockHeld && + (res->flags & VIR_LOCK_SPACE_ACQUIRE_AUTOCREATE)) { + if (res->flags & VIR_LOCK_SPACE_ACQUIRE_SHARED) { + /* We must upgrade to an exclusive lock to ensure + * no one else still has it before trying to delete */ + if (virFileLock(res->fd, false, 0, 1) < 0) { + VIR_DEBUG("Could not upgrade shared lease to exclusive, not deleting"); + } else { + unlink(res->path); + } + } else { + unlink(res->path); + } + } + + VIR_FORCE_CLOSE(res->fd); + VIR_FREE(res->path); + VIR_FREE(res->name); + VIR_FREE(res); +} + + +static virLockSpaceResourcePtr +virLockSpaceResourceNew(virLockSpacePtr lockspace, + const char *resname, + unsigned int flags, + pid_t owner) +{ + virLockSpaceResourcePtr res; + bool shared = !!(flags & VIR_LOCK_SPACE_ACQUIRE_SHARED); + + if (VIR_ALLOC(res) < 0) + return NULL; + + res->fd = -1; + res->flags = flags; + + if (!(res->name = strdup(resname))) + goto no_memory; + + if (!(res->path = virLockSpaceGetResourcePath(lockspace, resname))) + goto no_memory; + + if (flags & VIR_LOCK_SPACE_ACQUIRE_AUTOCREATE) { + while (1) { + struct stat a, b; + if ((res->fd = open(res->path, O_RDWR|O_CREAT, 0600)) < 0) { + virReportSystemError(errno, + _("Unable to open/create resource %s"), + res->path); + goto error; + } + + if (virSetCloseExec(res->fd) < 0) { + virReportSystemError(errno, + _("Failed to set close-on-exec flag '%s'"), + res->path); + goto error; + } + + if (fstat(res->fd, &b) < 0) { + virReportSystemError(errno, + _("Unable to check status of pid file '%s'"), + res->path); + goto error; + } + + if (virFileLock(res->fd, shared, 0, 1) < 0) { + if (errno == EACCES || errno == EAGAIN) { + virReportError(VIR_ERR_RESOURCE_BUSY, + _("Lockspace resource '%s' is locked"), + resname); + } else { + virReportSystemError(errno, + _("Unable to acquire lock on '%s'"), + res->path); + } + goto error; + } + + /* Now make sure the pidfile we locked is the same + * one that now exists on the filesystem + */ + if (stat(res->path, &a) < 0) { + char ebuf[1024] ATTRIBUTE_UNUSED; + VIR_DEBUG("Resource '%s' disappeared: %s", + res->path, virStrerror(errno, ebuf, sizeof(ebuf))); + VIR_FORCE_CLOSE(res->fd); + /* Someone else must be racing with us, so try agin */ + continue; + } + + if (a.st_ino == b.st_ino) + break; + + VIR_DEBUG("Resource '%s' was recreated", res->path); + VIR_FORCE_CLOSE(res->fd); + /* Someone else must be racing with us, so try agin */ + } + } else { + if ((res->fd = open(res->path, O_RDWR)) < 0) { + virReportSystemError(errno, + _("Unable to open resource %s"), + res->path); + goto error; + } + + if (virSetCloseExec(res->fd) < 0) { + virReportSystemError(errno, + _("Failed to set close-on-exec flag '%s'"), + res->path); + goto error; + } + + if (virFileLock(res->fd, !!(flags & VIR_LOCK_SPACE_ACQUIRE_SHARED), 0, 0) < 0) { + if (errno == EACCES || errno == EAGAIN) { + virReportError(VIR_ERR_RESOURCE_BUSY, + _("Lockspace resource '%s' is locked"), + resname); + } else { + virReportSystemError(errno, + _("Unable to acquire lock on '%s'"), + res->path); + } + goto error; + } + } + res->lockHeld = true; + + if (VIR_EXPAND_N(res->owners, res->nOwners, 1) < 0) + goto no_memory; + + res->owners[res->nOwners-1] = owner; + + return res; + +no_memory: + virReportOOMError(); +error: + virLockSpaceResourceFree(res); + return NULL; +} + + +static void virLockSpaceResourceDataFree(void *opaque, const void *name ATTRIBUTE_UNUSED) +{ + virLockSpaceResourcePtr res = opaque; + virLockSpaceResourceFree(res); +} + + +virLockSpacePtr virLockSpaceNew(const char *directory) +{ + virLockSpacePtr lockspace; + + VIR_DEBUG("directory=%s", NULLSTR(directory)); + + if (VIR_ALLOC(lockspace) < 0) + return NULL; + + if (virMutexInit(&lockspace->lock) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Unable to initialize lockspace mutex")); + VIR_FREE(lockspace); + return NULL; + } + + if (directory && + !(lockspace->dir = strdup(directory))) + goto no_memory; + + if (!(lockspace->resources = virHashCreate(10, + virLockSpaceResourceDataFree))) + goto error; + + if (directory) { + if (virFileExists(directory)) { + if (!virFileIsDir(directory)) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Lockspace location %s exists, but is not a directory"), + directory); + goto error; + } + } else { + if (virFileMakePathWithMode(directory, 0700) < 0) { + virReportSystemError(errno, + _("Unable to create lockspace %s"), + directory); + goto error; + } + } + } + + return lockspace; + +no_memory: + virReportOOMError(); +error: + virLockSpaceFree(lockspace); + return NULL; +} + + +void virLockSpaceFree(virLockSpacePtr lockspace) +{ + if (!lockspace) + return; + + virHashFree(lockspace->resources); + VIR_FREE(lockspace->dir); + virMutexDestroy(&lockspace->lock); + VIR_FREE(lockspace); +} + + +const char *virLockSpaceGetDirectory(virLockSpacePtr lockspace) +{ + return lockspace->dir; +} + + +int virLockSpaceCreateResource(virLockSpacePtr lockspace, + const char *resname) +{ + int ret = -1; + char *respath = NULL; + virLockSpaceResourcePtr res; + + VIR_DEBUG("lockspace=%p resname=%s", lockspace, resname); + + virMutexLock(&lockspace->lock); + + if ((res = virHashLookup(lockspace->resources, resname))) { + virReportError(VIR_ERR_RESOURCE_BUSY, + _("Lockspace resource '%s' is locked"), + resname); + goto cleanup; + } + + if (!(respath = virLockSpaceGetResourcePath(lockspace, resname))) + goto cleanup; + + if (virFileTouch(respath, 0600) < 0) + goto cleanup; + + ret = 0; + +cleanup: + virMutexUnlock(&lockspace->lock); + VIR_FREE(respath); + return ret; +} + + +int virLockSpaceDeleteResource(virLockSpacePtr lockspace, + const char *resname) +{ + int ret = -1; + char *respath = NULL; + virLockSpaceResourcePtr res; + + VIR_DEBUG("lockspace=%p resname=%s", lockspace, resname); + + virMutexLock(&lockspace->lock); + + if ((res = virHashLookup(lockspace->resources, resname))) { + virReportError(VIR_ERR_RESOURCE_BUSY, + _("Lockspace resource '%s' is locked"), + resname); + goto cleanup; + } + + if (!(respath = virLockSpaceGetResourcePath(lockspace, resname))) + goto cleanup; + + if (unlink(respath) < 0 && + errno != ENOENT) { + virReportSystemError(errno, + _("Unable to delete lockspace resource %s"), + respath); + goto cleanup; + } + + ret = 0; + +cleanup: + virMutexUnlock(&lockspace->lock); + VIR_FREE(respath); + return ret; +} + + +int virLockSpaceAcquireResource(virLockSpacePtr lockspace, + const char *resname, + pid_t owner, + unsigned int flags) +{ + int ret = -1; + virLockSpaceResourcePtr res; + + VIR_DEBUG("lockspace=%p resname=%s flags=%x owner=%lld", + lockspace, resname, flags, (unsigned long long)owner); + + virCheckFlags(VIR_LOCK_SPACE_ACQUIRE_SHARED | + VIR_LOCK_SPACE_ACQUIRE_AUTOCREATE, -1); + + virMutexLock(&lockspace->lock); + + if ((res = virHashLookup(lockspace->resources, resname))) { + if ((res->flags & VIR_LOCK_SPACE_ACQUIRE_SHARED) && + (flags & VIR_LOCK_SPACE_ACQUIRE_SHARED)) { + + if (VIR_EXPAND_N(res->owners, res->nOwners, 1) < 0) { + virReportOOMError(); + goto cleanup; + } + res->owners[res->nOwners-1] = owner; + + goto done; + } + virReportError(VIR_ERR_RESOURCE_BUSY, + _("Lockspace resource '%s' is locked"), + resname); + goto cleanup; + } + + if (!(res = virLockSpaceResourceNew(lockspace, resname, flags, owner))) + goto cleanup; + + if (virHashAddEntry(lockspace->resources, resname, res) < 0) { + virLockSpaceResourceFree(res); + goto cleanup; + } + +done: + ret = 0; + +cleanup: + virMutexUnlock(&lockspace->lock); + return ret; +} + + +int virLockSpaceReleaseResource(virLockSpacePtr lockspace, + const char *resname, + pid_t owner) +{ + int ret = -1; + virLockSpaceResourcePtr res; + size_t i; + + VIR_DEBUG("lockspace=%p resname=%s owner=%lld", + lockspace, resname, (unsigned long long)owner); + + virMutexLock(&lockspace->lock); + + if (!(res = virHashLookup(lockspace->resources, resname))) { + virReportError(VIR_ERR_RESOURCE_BUSY, + _("Lockspace resource '%s' is not locked"), + resname); + goto cleanup; + } + + for (i = 0 ; i < res->nOwners ; i++) { + if (res->owners[i] == owner) { + break; + } + } + + if (i == res->nOwners) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("owner %lld does not hold the resource lock"), + (unsigned long long)owner); + goto cleanup; + } + + if (i < (res->nOwners - 1)) + memmove(res->owners + i, + res->owners + i + 1, + (res->nOwners - i - 1) * sizeof(res->owners[0])); + VIR_SHRINK_N(res->owners, res->nOwners, 1); + + if ((res->nOwners == 0) && + virHashRemoveEntry(lockspace->resources, resname) < 0) + goto cleanup; + + ret = 0; + +cleanup: + virMutexUnlock(&lockspace->lock); + return ret; +} + + +struct virLockSpaceRemoveData { + pid_t owner; + size_t count; +}; + + +static int +virLockSpaceRemoveResourcesForOwner(const void *payload, + const void *name ATTRIBUTE_UNUSED, + const void *opaque) +{ + virLockSpaceResourcePtr res = (virLockSpaceResourcePtr)payload; + struct virLockSpaceRemoveData *data = (struct virLockSpaceRemoveData *)opaque; + size_t i; + + VIR_DEBUG("res %s owner %lld", res->name, (unsigned long long)data->owner); + + for (i = 0 ; i < res->nOwners ; i++) { + if (res->owners[i] == data->owner) { + break; + } + } + + if (i == res->nOwners) + return 0; + + data->count++; + + if (i < (res->nOwners - 1)) + memmove(res->owners + i, + res->owners + i + 1, + (res->nOwners - i - 1) * sizeof(res->owners[0])); + VIR_SHRINK_N(res->owners, res->nOwners, 1); + + if (res->nOwners) { + VIR_DEBUG("Other shared owners remain"); + return 0; + } + + VIR_DEBUG("No more owners, remove it"); + return 1; +} + + +int virLockSpaceReleaseResourcesForOwner(virLockSpacePtr lockspace, + pid_t owner) +{ + int ret = 0; + struct virLockSpaceRemoveData data = { + owner, 0 + }; + + VIR_DEBUG("lockspace=%p owner=%lld", lockspace, (unsigned long long)owner); + + virMutexLock(&lockspace->lock); + + if (virHashRemoveSet(lockspace->resources, + virLockSpaceRemoveResourcesForOwner, + &data) < 0) + goto error; + + ret = data.count; + + virMutexUnlock(&lockspace->lock); + return ret; + +error: + virMutexUnlock(&lockspace->lock); + return -1; +} diff --git a/src/util/virlockspace.h b/src/util/virlockspace.h new file mode 100644 index 0000000..bd8f91c --- /dev/null +++ b/src/util/virlockspace.h @@ -0,0 +1,58 @@ +/* + * virlockspace.h: simple file based lockspaces + * + * Copyright (C) 2012 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/>. + * + */ + +#ifndef __VIR_LOCK_SPACE_H__ +# define __VIR_LOCK_SPACE_H__ + +# include "internal.h" + +typedef struct _virLockSpace virLockSpace; +typedef virLockSpace *virLockSpacePtr; + +virLockSpacePtr virLockSpaceNew(const char *directory); + +void virLockSpaceFree(virLockSpacePtr lockspace); + +const char *virLockSpaceGetDirectory(virLockSpacePtr lockspace); + +int virLockSpaceCreateResource(virLockSpacePtr lockspace, + const char *resname); +int virLockSpaceDeleteResource(virLockSpacePtr lockspace, + const char *resname); + +typedef enum { + VIR_LOCK_SPACE_ACQUIRE_SHARED = (1 << 0), + VIR_LOCK_SPACE_ACQUIRE_AUTOCREATE = (1 << 1), +} virLockSpaceAcquireFlags; + +int virLockSpaceAcquireResource(virLockSpacePtr lockspace, + const char *resname, + pid_t owner, + unsigned int flags); + +int virLockSpaceReleaseResource(virLockSpacePtr lockspace, + const char *resname, + pid_t owner); + +int virLockSpaceReleaseResourcesForOwner(virLockSpacePtr lockspace, + pid_t owner); + +#endif /* __VIR_LOCK_SPACE_H__ */ diff --git a/src/util/virterror.c b/src/util/virterror.c index a40cfe0..74f511a 100644 --- a/src/util/virterror.c +++ b/src/util/virterror.c @@ -112,7 +112,8 @@ VIR_ENUM_IMPL(virErrorDomain, VIR_ERR_DOMAIN_LAST, "URI Utils", /* 45 */ "Authentication Utils", "DBus Utils", - "Parallels Cloud Server" + "Parallels Cloud Server", + "Lock Space", ) @@ -1185,6 +1186,12 @@ virErrorMsg(virErrorNumber error, const char *info) else errmsg = _("block copy still active: %s"); break; + case VIR_ERR_RESOURCE_BUSY: + if (info == NULL) + errmsg = _("resource busy"); + else + errmsg = _("resource busy %s"); + break; } return errmsg; } diff --git a/tests/Makefile.am b/tests/Makefile.am index 60d322d..6ff51af 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -92,7 +92,7 @@ test_programs = virshtest sockettest \ viratomictest \ utiltest virnettlscontexttest shunloadtest \ virtimetest viruritest virkeyfiletest \ - virauthconfigtest + virauthconfigtest virlockspacetest if WITH_DRIVER_MODULES test_programs += virdrivermoduletest @@ -504,6 +504,11 @@ virtimetest_SOURCES = \ virtimetest_CFLAGS = -Dabs_builddir="\"$(abs_builddir)\"" $(AM_CFLAGS) virtimetest_LDADD = $(LDADDS) +virlockspacetest_SOURCES = \ + virlockspacetest.c testutils.h testutils.c +virlockspacetest_CFLAGS = -Dabs_builddir="\"$(abs_builddir)\"" $(AM_CFLAGS) +virlockspacetest_LDADD = $(LDADDS) + viruritest_SOURCES = \ viruritest.c testutils.h testutils.c viruritest_CFLAGS = -Dabs_builddir="\"$(abs_builddir)\"" $(AM_CFLAGS) diff --git a/tests/virlockspacetest.c b/tests/virlockspacetest.c new file mode 100644 index 0000000..ee58f95 --- /dev/null +++ b/tests/virlockspacetest.c @@ -0,0 +1,363 @@ +/* + * Copyright (C) 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 + * <http://www.gnu.org/licenses/>. + * + * Author: Daniel P. Berrange <berrange@redhat.com> + */ + +#include <config.h> + +#include <stdlib.h> +#include <signal.h> +#include <sys/stat.h> + +#include "testutils.h" +#include "util.h" +#include "virterror_internal.h" +#include "memory.h" +#include "logging.h" + +#include "virlockspace.h" + +#define VIR_FROM_THIS VIR_FROM_RPC + +#define LOCKSPACE_DIR abs_builddir "/virlockspacedata" + +static int testLockSpaceCreate(const void *args ATTRIBUTE_UNUSED) +{ + virLockSpacePtr lockspace; + int ret = -1; + + rmdir(LOCKSPACE_DIR); + + lockspace = virLockSpaceNew(LOCKSPACE_DIR); + + if (!virFileIsDir(LOCKSPACE_DIR)) + goto cleanup; + + ret = 0; + +cleanup: + virLockSpaceFree(lockspace); + rmdir(LOCKSPACE_DIR); + return ret; +} + + +static int testLockSpaceResourceLifecycle(const void *args ATTRIBUTE_UNUSED) +{ + virLockSpacePtr lockspace; + int ret = -1; + + rmdir(LOCKSPACE_DIR); + + lockspace = virLockSpaceNew(LOCKSPACE_DIR); + + if (!virFileIsDir(LOCKSPACE_DIR)) + goto cleanup; + + if (virLockSpaceCreateResource(lockspace, "foo") < 0) + goto cleanup; + + if (!virFileExists(LOCKSPACE_DIR "/foo")) + goto cleanup; + + if (virLockSpaceDeleteResource(lockspace, "foo") < 0) + goto cleanup; + + if (virFileExists(LOCKSPACE_DIR "/foo")) + goto cleanup; + + ret = 0; + +cleanup: + virLockSpaceFree(lockspace); + rmdir(LOCKSPACE_DIR); + return ret; +} + + +static int testLockSpaceResourceLockExcl(const void *args ATTRIBUTE_UNUSED) +{ + virLockSpacePtr lockspace; + int ret = -1; + + rmdir(LOCKSPACE_DIR); + + lockspace = virLockSpaceNew(LOCKSPACE_DIR); + + if (!virFileIsDir(LOCKSPACE_DIR)) + goto cleanup; + + if (virLockSpaceCreateResource(lockspace, "foo") < 0) + goto cleanup; + + if (virLockSpaceAcquireResource(lockspace, "foo", geteuid(), 0) < 0) + goto cleanup; + + if (!virFileExists(LOCKSPACE_DIR "/foo")) + goto cleanup; + + if (virLockSpaceAcquireResource(lockspace, "foo", geteuid(), 0) == 0) + goto cleanup; + + if (virLockSpaceDeleteResource(lockspace, "foo") == 0) + goto cleanup; + + if (virLockSpaceReleaseResource(lockspace, "foo", geteuid()) < 0) + goto cleanup; + + if (virLockSpaceDeleteResource(lockspace, "foo") < 0) + goto cleanup; + + if (virFileExists(LOCKSPACE_DIR "/foo")) + goto cleanup; + + ret = 0; + +cleanup: + virLockSpaceFree(lockspace); + rmdir(LOCKSPACE_DIR); + return ret; +} + + +static int testLockSpaceResourceLockExclAuto(const void *args ATTRIBUTE_UNUSED) +{ + virLockSpacePtr lockspace; + int ret = -1; + + rmdir(LOCKSPACE_DIR); + + lockspace = virLockSpaceNew(LOCKSPACE_DIR); + + if (!virFileIsDir(LOCKSPACE_DIR)) + goto cleanup; + + if (virLockSpaceCreateResource(lockspace, "foo") < 0) + goto cleanup; + + if (virLockSpaceAcquireResource(lockspace, "foo", geteuid(), + VIR_LOCK_SPACE_ACQUIRE_AUTOCREATE) < 0) + goto cleanup; + + if (!virFileExists(LOCKSPACE_DIR "/foo")) + goto cleanup; + + if (virLockSpaceReleaseResource(lockspace, "foo", geteuid()) < 0) + goto cleanup; + + if (virFileExists(LOCKSPACE_DIR "/foo")) + goto cleanup; + + ret = 0; + +cleanup: + virLockSpaceFree(lockspace); + rmdir(LOCKSPACE_DIR); + return ret; +} + + +static int testLockSpaceResourceLockShr(const void *args ATTRIBUTE_UNUSED) +{ + virLockSpacePtr lockspace; + int ret = -1; + + rmdir(LOCKSPACE_DIR); + + lockspace = virLockSpaceNew(LOCKSPACE_DIR); + + if (!virFileIsDir(LOCKSPACE_DIR)) + goto cleanup; + + if (virLockSpaceCreateResource(lockspace, "foo") < 0) + goto cleanup; + + if (virLockSpaceAcquireResource(lockspace, "foo", geteuid(), + VIR_LOCK_SPACE_ACQUIRE_SHARED) < 0) + goto cleanup; + + if (virLockSpaceAcquireResource(lockspace, "foo", geteuid(), 0) == 0) + goto cleanup; + + if (virLockSpaceAcquireResource(lockspace, "foo", geteuid(), + VIR_LOCK_SPACE_ACQUIRE_SHARED) < 0) + goto cleanup; + + if (virLockSpaceDeleteResource(lockspace, "foo") == 0) + goto cleanup; + + if (virLockSpaceReleaseResource(lockspace, "foo", geteuid()) < 0) + goto cleanup; + + if (virLockSpaceDeleteResource(lockspace, "foo") == 0) + goto cleanup; + + if (virLockSpaceReleaseResource(lockspace, "foo", geteuid()) < 0) + goto cleanup; + + if (virLockSpaceDeleteResource(lockspace, "foo") < 0) + goto cleanup; + + if (virFileExists(LOCKSPACE_DIR "/foo")) + goto cleanup; + + ret = 0; + +cleanup: + virLockSpaceFree(lockspace); + rmdir(LOCKSPACE_DIR); + return ret; +} + + +static int testLockSpaceResourceLockShrAuto(const void *args ATTRIBUTE_UNUSED) +{ + virLockSpacePtr lockspace; + int ret = -1; + + rmdir(LOCKSPACE_DIR); + + lockspace = virLockSpaceNew(LOCKSPACE_DIR); + + if (!virFileIsDir(LOCKSPACE_DIR)) + goto cleanup; + + if (virLockSpaceCreateResource(lockspace, "foo") < 0) + goto cleanup; + + if (virLockSpaceAcquireResource(lockspace, "foo", geteuid(), + VIR_LOCK_SPACE_ACQUIRE_SHARED | + VIR_LOCK_SPACE_ACQUIRE_AUTOCREATE) < 0) + goto cleanup; + + if (!virFileExists(LOCKSPACE_DIR "/foo")) + goto cleanup; + + if (virLockSpaceAcquireResource(lockspace, "foo", geteuid(), + VIR_LOCK_SPACE_ACQUIRE_AUTOCREATE) == 0) + goto cleanup; + + if (!virFileExists(LOCKSPACE_DIR "/foo")) + goto cleanup; + + if (virLockSpaceAcquireResource(lockspace, "foo", geteuid(), + VIR_LOCK_SPACE_ACQUIRE_SHARED | + VIR_LOCK_SPACE_ACQUIRE_AUTOCREATE) < 0) + goto cleanup; + + if (!virFileExists(LOCKSPACE_DIR "/foo")) + goto cleanup; + + if (virLockSpaceReleaseResource(lockspace, "foo", geteuid()) < 0) + goto cleanup; + + if (!virFileExists(LOCKSPACE_DIR "/foo")) + goto cleanup; + + if (virLockSpaceReleaseResource(lockspace, "foo", geteuid()) < 0) + goto cleanup; + + if (virFileExists(LOCKSPACE_DIR "/foo")) + goto cleanup; + + ret = 0; + +cleanup: + virLockSpaceFree(lockspace); + rmdir(LOCKSPACE_DIR); + return ret; +} + + +static int testLockSpaceResourceLockPath(const void *args ATTRIBUTE_UNUSED) +{ + virLockSpacePtr lockspace; + int ret = -1; + + rmdir(LOCKSPACE_DIR); + + lockspace = virLockSpaceNew(NULL); + + mkdir(LOCKSPACE_DIR, 0700); + + if (virLockSpaceCreateResource(lockspace, LOCKSPACE_DIR "/foo") < 0) + goto cleanup; + + if (virLockSpaceAcquireResource(lockspace, LOCKSPACE_DIR "/foo", geteuid(), 0) < 0) + goto cleanup; + + if (!virFileExists(LOCKSPACE_DIR "/foo")) + goto cleanup; + + if (virLockSpaceAcquireResource(lockspace, LOCKSPACE_DIR "/foo", geteuid(), 0) == 0) + goto cleanup; + + if (virLockSpaceDeleteResource(lockspace, LOCKSPACE_DIR "/foo") == 0) + goto cleanup; + + if (virLockSpaceReleaseResource(lockspace, LOCKSPACE_DIR "/foo", geteuid()) < 0) + goto cleanup; + + if (virLockSpaceDeleteResource(lockspace, LOCKSPACE_DIR "/foo") < 0) + goto cleanup; + + if (virFileExists(LOCKSPACE_DIR "/foo")) + goto cleanup; + + ret = 0; + +cleanup: + virLockSpaceFree(lockspace); + rmdir(LOCKSPACE_DIR); + return ret; +} + + + +static int +mymain(void) +{ + int ret = 0; + + signal(SIGPIPE, SIG_IGN); + + if (virtTestRun("Lockspace creation", 1, testLockSpaceCreate, NULL) < 0) + ret = -1; + + if (virtTestRun("Lockspace res lifecycle", 1, testLockSpaceResourceLifecycle, NULL) < 0) + ret = -1; + + if (virtTestRun("Lockspace res lock excl", 1, testLockSpaceResourceLockExcl, NULL) < 0) + ret = -1; + + if (virtTestRun("Lockspace res lock shr", 1, testLockSpaceResourceLockShr, NULL) < 0) + ret = -1; + + if (virtTestRun("Lockspace res lock excl auto", 1, testLockSpaceResourceLockExclAuto, NULL) < 0) + ret = -1; + + if (virtTestRun("Lockspace res lock shr auto", 1, testLockSpaceResourceLockShrAuto, NULL) < 0) + ret = -1; + + if (virtTestRun("Lockspace res full path", 1, testLockSpaceResourceLockPath, NULL) < 0) + ret = -1; + + return ret==0 ? EXIT_SUCCESS : EXIT_FAILURE; +} + +VIRT_TEST_MAIN(mymain) -- 1.7.11.2

On 08/09/2012 09:20 AM, Daniel P. Berrange wrote: > From: "Daniel P. Berrange" <berrange@redhat.com> > > The previously introduced virFile{Lock,Unlock} APIs provide a > way to acquire/release fcntl() locks on individual files. For > unknown reason though, the POSIX spec says that fcntl() locks > are released when *any* file handle refering to the same path s/refering/referring/ > is closed. In the following sequence > > threadA: fd1 = open("foo") > threadB: fd2 = open("foo") > threadA: virFileLock(fd1) > threadB: virFileLock(fd2) > threadB: close(fd2) > > you'd expect threadA to come out holding a lock on 'foo', and > indeed it does hold a lock for a very short time. Unfortunately > when threadB does close(fd2) this releases the lock associated > with fd1. For the current libvirt use case for virFileLock - > pidfiles - this doesn't matter since the lock is acquired > at startup while single threaded an never released until > exit. > > To provide a more generally useful API though, it is neccessary s/neccessary/necessary/ > to introduce a slightly higher level abstraction, which is to > be referred to as a "lockspace". This is to be provided by > a virLockSpacePtr object in src/util/virlockspace.{c,h}. The > core idea is that the lockspace keeps track of what files are > already open+locked. This means that when a 2nd thread comes > along and tries to acquire a lock, it doesn't end up opening > and closing a new FD. The lockspace just checks the current > list of held locks and immediately returns VIR_ERR_RESOURCE_BUSY. > > NB, the API as it stands is designed on the basis that the > files being locked are not being otherwise opened and used > by the application code. One approach to using this API is to > acquire locks based on a hash of the filepath. > > eg to lock /var/lib/libvirt/images/foo.img the application > might do > > virLockSpacePtr lockspace = virLockSpaceNew("/var/lib/libvirt/imagelocks"); > lockname = md5sum("/var/lib/libvirt/images/foo.img") > virLockSpaceAcquireLock(lockspace, lockname) s/)$/),/g Don't you want to ensure that the canonical name is used (that is, symlinks can allow two different names to resolve to the same file, but you want to hash the name without symlinks). > > It is also possible to do locks directly on resources by > using a NULL lockspace directory and then using the file > path as the lock name eg > > virLockSpacePtr lockspace = virLockSpaceNew(NULL) > virLockSpaceAcquireLock(lockspace, "/var/lib/libvirt/images/foo.img") more missing semicolons > > This is only safe todo though if no other part of the proces s/todo/to do/; s/proces/process/ > will be opening the files. This will be the case when this > code is used inside the soon-to-be-reposted virlockd daemon > > Signed-off-by: Daniel P. Berrange <berrange@redhat.com> > --- This patch failed to apply directly for me; you'll have to rebase it again. I was able to figure out the changes, though (a new error in virterror.[ch]). > + > + > +static char *virLockSpaceGetResourcePath(virLockSpacePtr lockspace, > + const char *resname) > +{ > + char *ret; > + if (lockspace->dir) { > + if (virAsprintf(&ret, "%s/%s", lockspace->dir, resname) < 0) { > + virReportOOMError(); > + return NULL; > + } > + } else { > + if (!(ret = strdup(resname))) { > + virReportOOMError(); > + return NULL; > + } > + } Fine as-is, but could be written shorter as: if (lockspace->dir) ignore_value(virAsprintf(&ret, "%s/%s", lockspace->dir, resname)); else ret = strdup(resname); if (!ret) { virReportOOMError(); return NULL; } > +static void virLockSpaceResourceFree(virLockSpaceResourcePtr res) > +{ > + if (!res) > + return; > + > + if (res->lockHeld && > + (res->flags & VIR_LOCK_SPACE_ACQUIRE_AUTOCREATE)) { > + if (res->flags & VIR_LOCK_SPACE_ACQUIRE_SHARED) { > + /* We must upgrade to an exclusive lock to ensure > + * no one else still has it before trying to delete */ > + if (virFileLock(res->fd, false, 0, 1) < 0) { > + VIR_DEBUG("Could not upgrade shared lease to exclusive, not deleting"); > + } else { > + unlink(res->path); Should we log failures to unlink()? > +static virLockSpaceResourcePtr > +virLockSpaceResourceNew(virLockSpacePtr lockspace, > + VIR_DEBUG("Resource '%s' disappeared: %s", > + res->path, virStrerror(errno, ebuf, sizeof(ebuf))); > + VIR_FORCE_CLOSE(res->fd); > + /* Someone else must be racing with us, so try agin */ s/agin/again/ > + > +void virLockSpaceFree(virLockSpacePtr lockspace) > +{ + if (!lockspace) > + return; > + > + virHashFree(lockspace->resources); > + VIR_FREE(lockspace->dir); Should we first try to rmdir() the directory, and silently ignore errors related to the directory still being full? > +++ b/tests/virlockspacetest.c > + > +static int testLockSpaceCreate(const void *args ATTRIBUTE_UNUSED) > +{ > + virLockSpacePtr lockspace; > + int ret = -1; > + > + rmdir(LOCKSPACE_DIR); No checks for errors? > + > + lockspace = virLockSpaceNew(LOCKSPACE_DIR); > + > + if (!virFileIsDir(LOCKSPACE_DIR)) > + goto cleanup; Although this will still fail if we ignored an earlier failure to rmdir() a non-directory, so I guess you're okay for test purposes. ACK with nits addressed. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On Thu, Aug 16, 2012 at 04:31:28PM -0600, Eric Blake wrote:
On 08/09/2012 09:20 AM, Daniel P. Berrange wrote:
eg to lock /var/lib/libvirt/images/foo.img the application might do
virLockSpacePtr lockspace = virLockSpaceNew("/var/lib/libvirt/imagelocks"); lockname = md5sum("/var/lib/libvirt/images/foo.img") virLockSpaceAcquireLock(lockspace, lockname)
s/)$/),/g
Don't you want to ensure that the canonical name is used (that is, symlinks can allow two different names to resolve to the same file, but you want to hash the name without symlinks).
Yes, I added a note that the filename should be canonicalized in this example
+static void virLockSpaceResourceFree(virLockSpaceResourcePtr res) +{ + if (!res) + return; + + if (res->lockHeld && + (res->flags & VIR_LOCK_SPACE_ACQUIRE_AUTOCREATE)) { + if (res->flags & VIR_LOCK_SPACE_ACQUIRE_SHARED) { + /* We must upgrade to an exclusive lock to ensure + * no one else still has it before trying to delete */ + if (virFileLock(res->fd, false, 0, 1) < 0) { + VIR_DEBUG("Could not upgrade shared lease to exclusive, not deleting"); + } else { + unlink(res->path);
Should we log failures to unlink()?
Yeah, worth while, so I changed it to if (unlink(res->path) < 0 && errno != ENOENT) { char ebuf[1024]; VIR_WARN("Failed to unlink resource %s: %s", res->path, virStrerror(ioError, ebuf, sizeof(ebuf))); }
+ +void virLockSpaceFree(virLockSpacePtr lockspace) +{ + if (!lockspace) + return; + + virHashFree(lockspace->resources); + VIR_FREE(lockspace->dir);
Should we first try to rmdir() the directory, and silently ignore errors related to the directory still being full?
I think it is best to just leave it alone. In most production setups I expect this directory would in fact be as NFS mount. Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

From: "Daniel P. Berrange" <berrange@redhat.com> Add two new APIs virLockSpaceNewPostExecRestart and virLockSpacePreExecRestart which allow a virLockSpacePtr object to be created from a JSON object and saved to a JSON object, for the purposes of re-exec'ing a process. As well as saving the state in JSON format, the second method will disable the O_CLOEXEC flag so that the open file descriptors are preserved across the process re-exec() Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/libvirt_private.syms | 2 + src/util/virlockspace.c | 236 +++++++++++++++++++++++++++++++++++++++++++++++ src/util/virlockspace.h | 4 + 3 files changed, 242 insertions(+) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index d05c246..6077aa0 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1295,6 +1295,8 @@ virLockSpaceDeleteResource; virLockSpaceFree; virLockSpaceGetDirectory; virLockSpaceNew; +virLockSpaceNewPostExecRestart; +virLockSpacePreExecRestart; virLockSpaceReleaseResource; virLockSpaceReleaseResourcesForOwner; diff --git a/src/util/virlockspace.c b/src/util/virlockspace.c index 47dde66..524b5fa 100644 --- a/src/util/virlockspace.c +++ b/src/util/virlockspace.c @@ -285,6 +285,242 @@ error: } + +virLockSpacePtr virLockSpaceNewPostExecRestart(virJSONValuePtr object) +{ + virLockSpacePtr lockspace; + virJSONValuePtr resources; + int n; + size_t i; + + VIR_DEBUG("object=%p", object); + + if (VIR_ALLOC(lockspace) < 0) + return NULL; + + if (virMutexInit(&lockspace->lock) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Unable to initialize lockspace mutex")); + VIR_FREE(lockspace); + return NULL; + } + + if (!(lockspace->resources = virHashCreate(10, + virLockSpaceResourceDataFree))) + goto error; + + if (virJSONValueObjectHasKey(object, "directory")) { + const char *dir = virJSONValueObjectGetString(object, "directory"); + if (!(lockspace->dir = strdup(dir))) { + virReportOOMError(); + goto error; + } + } + + if (!(resources = virJSONValueObjectGet(object, "resources"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing resources value in JSON document")); + goto error; + } + + if ((n = virJSONValueArraySize(resources)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Malformed resources value in JSON document")); + goto error; + } + + for (i = 0 ; i < n ; i++) { + virJSONValuePtr child = virJSONValueArrayGet(resources, i); + virLockSpaceResourcePtr res; + const char *tmp; + virJSONValuePtr owners; + size_t j; + int m; + + if (VIR_ALLOC(res) < 0) { + virReportOOMError(); + goto error; + } + res->fd = -1; + + if (!(tmp = virJSONValueObjectGetString(child, "name"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing resource name in JSON document")); + virLockSpaceResourceFree(res); + goto error; + } + if (!(res->name = strdup(tmp))) { + virReportOOMError(); + virLockSpaceResourceFree(res); + goto error; + } + + if (!(tmp = virJSONValueObjectGetString(child, "path"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing resource path in JSON document")); + virLockSpaceResourceFree(res); + goto error; + } + if (!(res->path = strdup(tmp))) { + virReportOOMError(); + virLockSpaceResourceFree(res); + goto error; + } + if (virJSONValueObjectGetNumberInt(child, "fd", &res->fd) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing resource fd in JSON document")); + virLockSpaceResourceFree(res); + goto error; + } + if (virSetInherit(res->fd, false) < 0) { + virReportSystemError(errno, "%s", + _("Cannot enable close-on-exec flag")); + goto error; + } + if (virJSONValueObjectGetBoolean(child, "lockHeld", &res->lockHeld) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing resource lockHeld in JSON document")); + virLockSpaceResourceFree(res); + goto error; + } + + if (virJSONValueObjectGetNumberUint(child, "flags", &res->flags) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing resource flags in JSON document")); + virLockSpaceResourceFree(res); + goto error; + } + + if (!(owners = virJSONValueObjectGet(child, "owners"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing resource owners in JSON document")); + virLockSpaceResourceFree(res); + goto error; + } + + if ((m = virJSONValueArraySize(owners)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Malformed owners value in JSON document")); + virLockSpaceResourceFree(res); + goto error; + } + + for (j = 0 ; j < m ; j++) { + unsigned long long int owner; + virJSONValuePtr ownerval = virJSONValueArrayGet(owners, j); + + if (virJSONValueGetNumberUlong(ownerval, &owner) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Malformed owner value in JSON document")); + virLockSpaceResourceFree(res); + goto error; + } + + if (VIR_EXPAND_N(res->owners, res->nOwners, 1) < 0) { + virReportOOMError(); + virLockSpaceResourceFree(res); + goto error; + } + + res->owners[res->nOwners-1] = (pid_t)owner; + } + + if (virHashAddEntry(lockspace->resources, res->name, res) < 0) { + virLockSpaceResourceFree(res); + goto error; + } + } + + return lockspace; + +error: + virLockSpaceFree(lockspace); + return NULL; +} + + +virJSONValuePtr virLockSpacePreExecRestart(virLockSpacePtr lockspace) +{ + virJSONValuePtr object = virJSONValueNewObject(); + virJSONValuePtr resources; + virHashKeyValuePairPtr pairs = NULL, tmp; + + if (!object) + return NULL; + + virMutexLock(&lockspace->lock); + + if (lockspace->dir && + virJSONValueObjectAppendString(object, "directory", lockspace->dir) < 0) + goto error; + + if (!(resources = virJSONValueNewArray())) + goto error; + + if (virJSONValueObjectAppend(object, "resources", resources) < 0) { + virJSONValueFree(resources); + goto error; + } + + tmp = pairs = virHashGetItems(lockspace->resources, NULL); + while (tmp && tmp->value) { + virLockSpaceResourcePtr res = (virLockSpaceResourcePtr)tmp->value; + virJSONValuePtr child = virJSONValueNewObject(); + virJSONValuePtr owners = NULL; + size_t i; + + if (virJSONValueArrayAppend(resources, child) < 0) { + virJSONValueFree(child); + goto error; + } + + if (virJSONValueObjectAppendString(child, "name", res->name) < 0 || + virJSONValueObjectAppendString(child, "path", res->path) < 0 || + virJSONValueObjectAppendNumberInt(child, "fd", res->fd) < 0 || + virJSONValueObjectAppendBoolean(child, "lockHeld", res->lockHeld) < 0 || + virJSONValueObjectAppendNumberUint(child, "flags", res->flags) < 0) + goto error; + + if (virSetInherit(res->fd, true) < 0) { + virReportSystemError(errno, "%s", + _("Cannot disable close-on-exec flag")); + goto error; + } + + if (!(owners = virJSONValueNewArray())) + goto error; + + if (virJSONValueObjectAppend(child, "owners", owners) < 0) { + virJSONValueFree(owners); + goto error; + } + + for (i = 0 ; i < res->nOwners ; i++) { + virJSONValuePtr owner = virJSONValueNewNumberUlong(res->owners[i]); + if (!owner) + goto error; + + if (virJSONValueArrayAppend(owners, owner) < 0) { + virJSONValueFree(owner); + goto error; + } + } + + tmp++; + } + VIR_FREE(pairs); + + virMutexUnlock(&lockspace->lock); + return object; + + error: + VIR_FREE(pairs); + virJSONValueFree(object); + virMutexUnlock(&lockspace->lock); + return NULL; +} + + void virLockSpaceFree(virLockSpacePtr lockspace) { if (!lockspace) diff --git a/src/util/virlockspace.h b/src/util/virlockspace.h index bd8f91c..9c5128b 100644 --- a/src/util/virlockspace.h +++ b/src/util/virlockspace.h @@ -23,11 +23,15 @@ # define __VIR_LOCK_SPACE_H__ # include "internal.h" +# include "json.h" typedef struct _virLockSpace virLockSpace; typedef virLockSpace *virLockSpacePtr; virLockSpacePtr virLockSpaceNew(const char *directory); +virLockSpacePtr virLockSpaceNewPostExecRestart(virJSONValuePtr object); + +virJSONValuePtr virLockSpacePreExecRestart(virLockSpacePtr lockspace); void virLockSpaceFree(virLockSpacePtr lockspace); -- 1.7.11.2

On 08/09/2012 09:20 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
Add two new APIs virLockSpaceNewPostExecRestart and virLockSpacePreExecRestart which allow a virLockSpacePtr object to be created from a JSON object and saved to a JSON object, for the purposes of re-exec'ing a process.
As well as saving the state in JSON format, the second method will disable the O_CLOEXEC flag so that the open file descriptors are preserved across the process re-exec()
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> ---
Is virLockSpacePreExecRestart called in the parent prior to forking (mostly good, with one caveat) or in the child between fork and exec (bad, since you malloc and do a lot of other non-async-safe stuff)? If the latter, we risk deadlock; if the former, then you have at least one bug...
+ +virLockSpacePtr virLockSpaceNewPostExecRestart(virJSONValuePtr object) +{ + virLockSpacePtr lockspace; + virJSONValuePtr resources; + int n; + size_t i; + + VIR_DEBUG("object=%p", object);
Would it be any better to pretty-print the contents of object instead of just listing its address?
+ if (virJSONValueObjectGetNumberInt(child, "fd", &res->fd) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing resource fd in JSON document")); + virLockSpaceResourceFree(res); + goto error; + } + if (virSetInherit(res->fd, false) < 0) { + virReportSystemError(errno, "%s", + _("Cannot enable close-on-exec flag")); + goto error;
Is the reparse loop called while the exec'd process is still single-threaded, so as to avoid any races with the fds that are not yet re-marked CLOEXEC?
+ +virJSONValuePtr virLockSpacePreExecRestart(virLockSpacePtr lockspace) +{
+ + if (virSetInherit(res->fd, true) < 0) { + virReportSystemError(errno, "%s", + _("Cannot disable close-on-exec flag")); + goto error; + }
...clearing O_CLOEXEC in the parent is wrong. You cannot clear O_CLOEXEC until you are in the child (so as not to leak into any other child process spawned by another thread), which really means that you have to use virCommandPreserveFD instead of virSetInherit (virCommand will then call virSetInherit in the child, between fork and exec, on your behalf). But that makes it sound like you need to pass a virCommandPtr in to this function, in addition to the lockspace. Overall, looks like a mostly sane serialization - simpler than coding it up in XML, and still robust to pass between parent and child, but you do need to solve the CLOEXEC safety issues. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On 08/16/2012 05:07 PM, Eric Blake wrote:
On 08/09/2012 09:20 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
Add two new APIs virLockSpaceNewPostExecRestart and virLockSpacePreExecRestart which allow a virLockSpacePtr object to be created from a JSON object and saved to a JSON object, for the purposes of re-exec'ing a process.
As well as saving the state in JSON format, the second method will disable the O_CLOEXEC flag so that the open file descriptors are preserved across the process re-exec()
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> ---
Is virLockSpacePreExecRestart called in the parent prior to forking (mostly good, with one caveat) or in the child between fork and exec (bad, since you malloc and do a lot of other non-async-safe stuff)? If the latter, we risk deadlock; if the former, then you have at least one bug...
Before you worry about responding to this, read my comments on 21/23. I think I've managed to convince myself that if these functions are only ever called from virtlockd (and _not_ from libvirt.so or libvirtd), then the fact that virtlockd is so dedicated-purpose as to never spawn a helper child means that you don't have to worry about FD_CLOEXEC races to arbitrary children. If you agree with my analysis, then this patch (and several like it where I complained about CLOEXEC races) should be okay as-is. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On Fri, Aug 17, 2012 at 11:25:37AM -0600, Eric Blake wrote:
On 08/16/2012 05:07 PM, Eric Blake wrote:
On 08/09/2012 09:20 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
Add two new APIs virLockSpaceNewPostExecRestart and virLockSpacePreExecRestart which allow a virLockSpacePtr object to be created from a JSON object and saved to a JSON object, for the purposes of re-exec'ing a process.
As well as saving the state in JSON format, the second method will disable the O_CLOEXEC flag so that the open file descriptors are preserved across the process re-exec()
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> ---
Is virLockSpacePreExecRestart called in the parent prior to forking (mostly good, with one caveat) or in the child between fork and exec (bad, since you malloc and do a lot of other non-async-safe stuff)? If the latter, we risk deadlock; if the former, then you have at least one bug...
Before you worry about responding to this, read my comments on 21/23. I think I've managed to convince myself that if these functions are only ever called from virtlockd (and _not_ from libvirt.so or libvirtd), then the fact that virtlockd is so dedicated-purpose as to never spawn a helper child means that you don't have to worry about FD_CLOEXEC races to arbitrary children. If you agree with my analysis, then this patch (and several like it where I complained about CLOEXEC races) should be okay as-is.
It is not so much that it will only be called from virtlockd, but rather that this must only be called from a state where the process is basically idle. The intent is that the process has exit'd its event loop, and has stopped all I/O threads, etc. Basically the intent is that the process call this method (and its similar friends), and then immediately execve() itself. It should not be doing anything else in parallel with this. Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

From: "Daniel P. Berrange" <berrange@redhat.com> Add two new APIs virNetSocketNewPostExecRestart and virNetSocketPreExecRestart which allow a virNetSocketPtr object to be created from a JSON object and saved to a JSON object, for the purpose of re-exec'ing a process. As well as saving the state in JSON format, the second method will disable the O_CLOEXEC flag so that the open file descriptors are preserved across the process re-exec() Since it is not possible to serialize SASL or TLS encryption state, an error will be raised if attempting to perform serialization on non-raw sockets Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/libvirt_private.syms | 2 + src/rpc/virnetsocket.c | 108 +++++++++++++++++++++++++++++++++++++++++++++++ src/rpc/virnetsocket.h | 6 +++ 3 files changed, 116 insertions(+) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 6077aa0..836a926 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1599,6 +1599,8 @@ virNetSocketNewConnectUNIX; virNetSocketNewListenFD; virNetSocketNewListenTCP; virNetSocketNewListenUNIX; +virNetSocketNewPostExecRestart; +virNetSocketPreExecRestart; virNetSocketRead; virNetSocketRecvFD; virNetSocketRemoteAddrString; diff --git a/src/rpc/virnetsocket.c b/src/rpc/virnetsocket.c index 69c25bd..b9d1d97 100644 --- a/src/rpc/virnetsocket.c +++ b/src/rpc/virnetsocket.c @@ -741,6 +741,114 @@ int virNetSocketNewConnectExternal(const char **cmdargv, } +virNetSocketPtr virNetSocketNewPostExecRestart(virJSONValuePtr object) +{ + virSocketAddr localAddr; + virSocketAddr remoteAddr; + int fd, thepid, errfd; + bool isClient; + + if (virJSONValueObjectGetNumberInt(object, "fd", &fd) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing fd data in JSON document")); + return NULL; + } + + if (virJSONValueObjectGetNumberInt(object, "pid", &thepid) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing pid data in JSON document")); + return NULL; + } + + if (virJSONValueObjectGetNumberInt(object, "errfd", &errfd) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing errfd data in JSON document")); + return NULL; + } + if (virJSONValueObjectGetBoolean(object, "isClient", &isClient) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing isClient data in JSON document")); + return NULL; + } + + memset(&localAddr, 0, sizeof(localAddr)); + memset(&remoteAddr, 0, sizeof(remoteAddr)); + + remoteAddr.len = sizeof(remoteAddr.data.stor); + if (getsockname(fd, &remoteAddr.data.sa, &remoteAddr.len) < 0) { + virReportSystemError(errno, "%s", _("Unable to get peer socket name")); + return NULL; + } + + localAddr.len = sizeof(localAddr.data.stor); + if (getsockname(fd, &localAddr.data.sa, &localAddr.len) < 0) { + virReportSystemError(errno, "%s", _("Unable to get local socket name")); + return NULL; + } + + return virNetSocketNew(&localAddr, &remoteAddr, + isClient, fd, errfd, thepid); +} + + +virJSONValuePtr virNetSocketPreExecRestart(virNetSocketPtr sock) +{ + virJSONValuePtr object = NULL; + + virMutexLock(&sock->lock); + +#if HAVE_SASL + if (sock->saslSession) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("Unable to save socket state when SASL session is active")); + goto error; + } +#endif + if (sock->tlsSession) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("Unable to save socket state when TLS session is active")); + goto error; + } + + if (!(object = virJSONValueNewObject())) + goto error; + + if (virJSONValueObjectAppendNumberInt(object, "fd", sock->fd) < 0) + goto error; + + if (virJSONValueObjectAppendNumberInt(object, "errfd", sock->errfd) < 0) + goto error; + + if (virJSONValueObjectAppendNumberInt(object, "pid", sock->pid) < 0) + goto error; + + if (virJSONValueObjectAppendBoolean(object, "isClient", sock->client) < 0) + goto error; + + if (virSetInherit(sock->fd, true) < 0) { + virReportSystemError(errno, + _("Cannot disable close-on-exec flag on socket %d"), + sock->fd); + goto error; + } + if (sock->errfd != -1 && + virSetInherit(sock->errfd, true) < 0) { + virReportSystemError(errno, + _("Cannot disable close-on-exec flag on pipe %d"), + sock->errfd); + goto error; + } + + virMutexUnlock(&sock->lock); + return object; + +error: + virMutexUnlock(&sock->lock); + virJSONValueFree(object); + return NULL; +} + + void virNetSocketDispose(void *obj) { virNetSocketPtr sock = obj; diff --git a/src/rpc/virnetsocket.h b/src/rpc/virnetsocket.h index 3750955..26ceac0 100644 --- a/src/rpc/virnetsocket.h +++ b/src/rpc/virnetsocket.h @@ -31,6 +31,7 @@ # ifdef HAVE_SASL # include "virnetsaslcontext.h" # endif +# include "json.h" typedef struct _virNetSocket virNetSocket; typedef virNetSocket *virNetSocketPtr; @@ -81,6 +82,11 @@ int virNetSocketNewConnectSSH(const char *nodename, int virNetSocketNewConnectExternal(const char **cmdargv, virNetSocketPtr *addr); + +virNetSocketPtr virNetSocketNewPostExecRestart(virJSONValuePtr object); + +virJSONValuePtr virNetSocketPreExecRestart(virNetSocketPtr sock); + int virNetSocketGetFD(virNetSocketPtr sock); int virNetSocketDupFD(virNetSocketPtr sock, bool cloexec); -- 1.7.11.2

On 08/09/2012 09:20 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
Add two new APIs virNetSocketNewPostExecRestart and virNetSocketPreExecRestart which allow a virNetSocketPtr object to be created from a JSON object and saved to a JSON object, for the purpose of re-exec'ing a process.
As well as saving the state in JSON format, the second method will disable the O_CLOEXEC flag so that the open file descriptors are preserved across the process re-exec()
Same problem as 12/23 regarding _when_ you clear O_CLOEXEC.
Since it is not possible to serialize SASL or TLS encryption state, an error will be raised if attempting to perform serialization on non-raw sockets
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/libvirt_private.syms | 2 + src/rpc/virnetsocket.c | 108 +++++++++++++++++++++++++++++++++++++++++++++++ src/rpc/virnetsocket.h | 6 +++ 3 files changed, 116 insertions(+)
+virNetSocketPtr virNetSocketNewPostExecRestart(virJSONValuePtr object) +{ + virSocketAddr localAddr; + virSocketAddr remoteAddr; + int fd, thepid, errfd; + bool isClient; + + if (virJSONValueObjectGetNumberInt(object, "fd", &fd) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing fd data in JSON document")); + return NULL; + } + + if (virJSONValueObjectGetNumberInt(object, "pid", &thepid) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing pid data in JSON document")); + return NULL; + } + + if (virJSONValueObjectGetNumberInt(object, "errfd", &errfd) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing errfd data in JSON document")); + return NULL; + }
Do you need to re-enable FD_CLOEXEC on fd and errfd at this point? -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On Thu, Aug 16, 2012 at 05:16:50PM -0600, Eric Blake wrote:
On 08/09/2012 09:20 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
Add two new APIs virNetSocketNewPostExecRestart and virNetSocketPreExecRestart which allow a virNetSocketPtr object to be created from a JSON object and saved to a JSON object, for the purpose of re-exec'ing a process.
As well as saving the state in JSON format, the second method will disable the O_CLOEXEC flag so that the open file descriptors are preserved across the process re-exec()
Same problem as 12/23 regarding _when_ you clear O_CLOEXEC.
Since it is not possible to serialize SASL or TLS encryption state, an error will be raised if attempting to perform serialization on non-raw sockets
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/libvirt_private.syms | 2 + src/rpc/virnetsocket.c | 108 +++++++++++++++++++++++++++++++++++++++++++++++ src/rpc/virnetsocket.h | 6 +++ 3 files changed, 116 insertions(+)
+virNetSocketPtr virNetSocketNewPostExecRestart(virJSONValuePtr object) +{ + virSocketAddr localAddr; + virSocketAddr remoteAddr; + int fd, thepid, errfd; + bool isClient; + + if (virJSONValueObjectGetNumberInt(object, "fd", &fd) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing fd data in JSON document")); + return NULL; + } + + if (virJSONValueObjectGetNumberInt(object, "pid", &thepid) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing pid data in JSON document")); + return NULL; + } + + if (virJSONValueObjectGetNumberInt(object, "errfd", &errfd) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing errfd data in JSON document")); + return NULL; + }
Do you need to re-enable FD_CLOEXEC on fd and errfd at this point?
In the scenario in which it is valid to call these APIs, the only thing the caller can do is to exit(), so I'd say we don't need to deal with re-enabling FD_CLOEXEC Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On 08/21/2012 09:48 AM, Daniel P. Berrange wrote:
On Thu, Aug 16, 2012 at 05:16:50PM -0600, Eric Blake wrote:
On 08/09/2012 09:20 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
Add two new APIs virNetSocketNewPostExecRestart and virNetSocketPreExecRestart which allow a virNetSocketPtr object to be created from a JSON object and saved to a JSON object, for the purpose of re-exec'ing a process.
As well as saving the state in JSON format, the second method will disable the O_CLOEXEC flag so that the open file descriptors are preserved across the process re-exec()
Same problem as 12/23 regarding _when_ you clear O_CLOEXEC.
+ if (virJSONValueObjectGetNumberInt(object, "errfd", &errfd) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing errfd data in JSON document")); + return NULL; + }
Do you need to re-enable FD_CLOEXEC on fd and errfd at this point?
In the scenario in which it is valid to call these APIs, the only thing the caller can do is to exit(), so I'd say we don't need to deal with re-enabling FD_CLOEXEC
Fair enough - I think I've managed to convince myself later in the series, and your replies confirm, that this API is safe _for the situation in which we are using it_; maybe a bit of documentation about why we are doing things that are normally unsafe in multithreaded programs that exec arbitrary children is in order (because although the virtlockd program is multi-threaded, it does not exec arbitrary children, and the one exec that it does do is at a safe point in processing). I'm not sure if you are planning on submitting a rebase (since there were other things I pointed out along the way that might need fixing), but you have my ACK on the design. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

From: "Daniel P. Berrange" <berrange@redhat.com> Add two new APIs virNetServerServiceNewPostExecRestart and virNetServerServicePreExecRestart which allow a virNetServerServicePtr object to be created from a JSON object and saved to a JSON object, for the purpose of re-exec'ing a process. This includes serialization of the listening sockets associated with the service Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- po/POTFILES.in | 1 + src/libvirt_private.syms | 2 + src/rpc/virnetserverservice.c | 124 ++++++++++++++++++++++++++++++++++++++++++ src/rpc/virnetserverservice.h | 4 ++ 4 files changed, 131 insertions(+) diff --git a/po/POTFILES.in b/po/POTFILES.in index 183ab42..c8dbe4b 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -97,6 +97,7 @@ src/rpc/virnetserver.c src/rpc/virnetserverclient.c src/rpc/virnetservermdns.c src/rpc/virnetserverprogram.c +src/rpc/virnetserverservice.c src/rpc/virnettlscontext.c src/secret/secret_driver.c src/security/security_apparmor.c diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 836a926..7ea4b95 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1571,8 +1571,10 @@ virNetServerServiceGetPort; virNetServerServiceGetTLSContext; virNetServerServiceIsReadonly; virNetServerServiceNewFD; +virNetServerServiceNewPostExecRestart; virNetServerServiceNewTCP; virNetServerServiceNewUNIX; +virNetServerServicePreExecRestart; virNetServerServiceSetDispatcher; virNetServerServiceToggle; diff --git a/src/rpc/virnetserverservice.c b/src/rpc/virnetserverservice.c index 53ff503..c31bff3 100644 --- a/src/rpc/virnetserverservice.c +++ b/src/rpc/virnetserverservice.c @@ -250,6 +250,130 @@ error: } +virNetServerServicePtr virNetServerServiceNewPostExecRestart(virJSONValuePtr object) +{ + virNetServerServicePtr svc; + virJSONValuePtr socks; + size_t i; + int n; + + if (virNetServerServiceInitialize() < 0) + return NULL; + + if (!(svc = virObjectNew(virNetServerServiceClass))) + return NULL; + + if (virJSONValueObjectGetNumberInt(object, "auth", &svc->auth) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing auth field in JSON state document")); + goto error; + } + if (virJSONValueObjectGetBoolean(object, "readonly", &svc->readonly) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing readonly field in JSON state document")); + goto error; + } + if (virJSONValueObjectGetNumberUint(object, "nrequests_client_max", + (unsigned int *)&svc->nrequests_client_max) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing nrequests_client_max field in JSON state document")); + goto error; + } + + if (!(socks = virJSONValueObjectGet(object, "socks"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing socks field in JSON state document")); + goto error; + } + + if ((n = virJSONValueArraySize(socks)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("socks field in JSON was not an array")); + goto error; + } + + for (i = 0 ; i < n ; i++) { + virJSONValuePtr child = virJSONValueArrayGet(socks, i); + virNetSocketPtr sock; + + if (!(sock = virNetSocketNewPostExecRestart(child))) { + virObjectUnref(sock); + goto error; + } + + if (VIR_EXPAND_N(svc->socks, svc->nsocks, 1) < 0) { + virReportOOMError(); + virObjectUnref(sock); + goto error; + } + + svc->socks[svc->nsocks-1] = sock; + + /* IO callback is initially disabled, until we're ready + * to deal with incoming clients */ + virObjectRef(svc); + if (virNetSocketAddIOCallback(sock, + 0, + virNetServerServiceAccept, + svc, + virObjectFreeCallback) < 0) { + virObjectUnref(svc); + virObjectUnref(sock); + goto error; + } + } + + return svc; + +error: + virObjectUnref(svc); + return NULL; +} + + +virJSONValuePtr virNetServerServicePreExecRestart(virNetServerServicePtr svc) +{ + virJSONValuePtr object = virJSONValueNewObject(); + virJSONValuePtr socks; + size_t i; + + if (!object) + return NULL; + + if (!(socks = virJSONValueNewArray())) + goto error; + + if (virJSONValueObjectAppendNumberInt(object, "auth", svc->auth) < 0) + goto error; + if (virJSONValueObjectAppendBoolean(object, "readonly", svc->readonly) < 0) + goto error; + if (virJSONValueObjectAppendNumberInt(object, "nrequests_client_max", svc->nrequests_client_max) < 0) + goto error; + + if (virJSONValueObjectAppend(object, "socks", socks) < 0) { + virJSONValueFree(socks); + goto error; + } + + for (i = 0 ; i < svc->nsocks ; i++) { + virJSONValuePtr child; + if (!(child = virNetSocketPreExecRestart(svc->socks[i]))) + goto error; + + if (virJSONValueArrayAppend(socks, child) < 0) { + virJSONValueFree(child); + goto error; + } + } + + return object; + +error: + virJSONValueFree(object); + return NULL; +} + + int virNetServerServiceGetPort(virNetServerServicePtr svc) { /* We're assuming if there are multiple sockets diff --git a/src/rpc/virnetserverservice.h b/src/rpc/virnetserverservice.h index 48f49e7..344d20c 100644 --- a/src/rpc/virnetserverservice.h +++ b/src/rpc/virnetserverservice.h @@ -56,6 +56,10 @@ virNetServerServicePtr virNetServerServiceNewFD(int fd, size_t nrequests_client_max, virNetTLSContextPtr tls); +virNetServerServicePtr virNetServerServiceNewPostExecRestart(virJSONValuePtr object); + +virJSONValuePtr virNetServerServicePreExecRestart(virNetServerServicePtr service); + int virNetServerServiceGetPort(virNetServerServicePtr svc); int virNetServerServiceGetAuth(virNetServerServicePtr svc); -- 1.7.11.2

On 08/09/2012 09:20 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
Add two new APIs virNetServerServiceNewPostExecRestart and virNetServerServicePreExecRestart which allow a virNetServerServicePtr object to be created from a JSON object and saved to a JSON object, for the purpose of re-exec'ing a process.
This includes serialization of the listening sockets associated with the service
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- po/POTFILES.in | 1 + src/libvirt_private.syms | 2 + src/rpc/virnetserverservice.c | 124 ++++++++++++++++++++++++++++++++++++++++++ src/rpc/virnetserverservice.h | 4 ++ 4 files changed, 131 insertions(+)
May need some signature changes if the solution to 12 and 13 requires passing a virCommandPtr through the PreExecRestart commands. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

From: "Daniel P. Berrange" <berrange@redhat.com> Add two new APIs virNetServerClientNewPostExecRestart and virNetServerClientPreExecRestart which allow a virNetServerClientPtr object to be created from a JSON object and saved to a JSON object, for the purpose of re-exec'ing a process. This includes serialization of the connected socket associated with the client Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- daemon/libvirtd.c | 1 + src/libvirt_private.syms | 2 + src/lxc/lxc_controller.c | 1 + src/rpc/virnetserver.c | 4 ++ src/rpc/virnetserver.h | 1 + src/rpc/virnetserverclient.c | 135 +++++++++++++++++++++++++++++++++++++++++++ src/rpc/virnetserverclient.h | 15 +++++ 7 files changed, 159 insertions(+) diff --git a/daemon/libvirtd.c b/daemon/libvirtd.c index 24e20f8..a411060 100644 --- a/daemon/libvirtd.c +++ b/daemon/libvirtd.c @@ -1196,6 +1196,7 @@ int main(int argc, char **argv) { !!config->keepalive_required, config->mdns_adv ? config->mdns_name : NULL, remoteClientInitHook, + NULL, remoteClientFreeFunc, NULL))) { ret = VIR_DAEMON_ERR_INIT; diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 7ea4b95..cdb9b57 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1526,6 +1526,8 @@ virNetServerClientIsSecure; virNetServerClientLocalAddrString; virNetServerClientNeedAuth; virNetServerClientNew; +virNetServerClientNewPostExecRestart; +virNetServerClientPreExecRestart; virNetServerClientRemoteAddrString; virNetServerClientRemoveFilter; virNetServerClientSendMessage; diff --git a/src/lxc/lxc_controller.c b/src/lxc/lxc_controller.c index 4c3c17f..af1c40e 100644 --- a/src/lxc/lxc_controller.c +++ b/src/lxc/lxc_controller.c @@ -616,6 +616,7 @@ static int virLXCControllerSetupServer(virLXCControllerPtr ctrl) -1, 0, false, NULL, virLXCControllerClientPrivateNew, + NULL, virLXCControllerClientPrivateFree, ctrl))) goto error; diff --git a/src/rpc/virnetserver.c b/src/rpc/virnetserver.c index 0a6ecdc..902c34e 100644 --- a/src/rpc/virnetserver.c +++ b/src/rpc/virnetserver.c @@ -105,6 +105,7 @@ struct _virNetServer { void *autoShutdownOpaque; virNetServerClientPrivNew clientPrivNew; + virNetServerClientPrivPreExecRestart clientPrivPreExecRestart; virFreeCallback clientPrivFree; void *clientPrivOpaque; }; @@ -312,6 +313,7 @@ static int virNetServerDispatchNewClient(virNetServerServicePtr svc, virNetServerServiceGetMaxRequests(svc), virNetServerServiceGetTLSContext(svc), srv->clientPrivNew, + srv->clientPrivPreExecRestart, srv->clientPrivFree, srv->clientPrivOpaque))) return -1; @@ -363,6 +365,7 @@ virNetServerPtr virNetServerNew(size_t min_workers, bool keepaliveRequired, const char *mdnsGroupName, virNetServerClientPrivNew clientPrivNew, + virNetServerClientPrivPreExecRestart clientPrivPreExecRestart, virFreeCallback clientPrivFree, void *clientPrivOpaque) { @@ -388,6 +391,7 @@ virNetServerPtr virNetServerNew(size_t min_workers, srv->keepaliveRequired = keepaliveRequired; srv->sigwrite = srv->sigread = -1; srv->clientPrivNew = clientPrivNew; + srv->clientPrivPreExecRestart = clientPrivPreExecRestart; srv->clientPrivFree = clientPrivFree; srv->clientPrivOpaque = clientPrivOpaque; srv->privileged = geteuid() == 0 ? true : false; diff --git a/src/rpc/virnetserver.h b/src/rpc/virnetserver.h index 9188072..778b069 100644 --- a/src/rpc/virnetserver.h +++ b/src/rpc/virnetserver.h @@ -41,6 +41,7 @@ virNetServerPtr virNetServerNew(size_t min_workers, bool keepaliveRequired, const char *mdnsGroupName, virNetServerClientPrivNew clientPrivNew, + virNetServerClientPrivPreExecRestart clientPrivPreExecRestart, virFreeCallback clientPrivFree, void *clientPrivOpaque); diff --git a/src/rpc/virnetserverclient.c b/src/rpc/virnetserverclient.c index acd2b4d..661242a 100644 --- a/src/rpc/virnetserverclient.c +++ b/src/rpc/virnetserverclient.c @@ -98,6 +98,7 @@ struct _virNetServerClient void *privateData; virFreeCallback privateDataFreeFunc; + virNetServerClientPrivPreExecRestart privateDataPreExecRestart; virNetServerClientCloseFunc privateDataCloseFunc; virKeepAlivePtr keepalive; @@ -395,6 +396,7 @@ virNetServerClientPtr virNetServerClientNew(virNetSocketPtr sock, size_t nrequests_max, virNetTLSContextPtr tls, virNetServerClientPrivNew privNew, + virNetServerClientPrivPreExecRestart privPreExecRestart, virFreeCallback privFree, void *privOpaque) { @@ -411,12 +413,145 @@ virNetServerClientPtr virNetServerClientNew(virNetSocketPtr sock, return NULL; } client->privateDataFreeFunc = privFree; + client->privateDataPreExecRestart = privPreExecRestart; } return client; } +virNetServerClientPtr virNetServerClientNewPostExecRestart(virJSONValuePtr object, + virNetServerClientPrivNewPostExecRestart privNew, + virNetServerClientPrivPreExecRestart privPreExecRestart, + virFreeCallback privFree, + void *privOpaque) +{ + virJSONValuePtr child; + virNetServerClientPtr client = NULL; + virNetSocketPtr sock; + const char *identity = NULL; + int auth; + bool readonly; + unsigned int nrequests_max; + + if (virJSONValueObjectGetNumberInt(object, "auth", &auth) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing auth field in JSON state document")); + return NULL; + } + if (virJSONValueObjectGetBoolean(object, "readonly", &readonly) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing readonly field in JSON state document")); + return NULL; + } + if (virJSONValueObjectGetNumberUint(object, "nrequests_max", + (unsigned int *)&nrequests_max) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing nrequests_client_max field in JSON state document")); + return NULL; + } + if (virJSONValueObjectHasKey(object, "identity") && + (!(identity = virJSONValueObjectGetString(object, "identity")))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing identity field in JSON state document")); + return NULL; + } + + if (!(child = virJSONValueObjectGet(object, "sock"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing sock field in JSON state document")); + return NULL; + } + + if (!(sock = virNetSocketNewPostExecRestart(child))) { + virObjectUnref(sock); + return NULL; + } + + if (!(client = virNetServerClientNewInternal(sock, + auth, + readonly, + nrequests_max, + NULL))) { + virObjectUnref(sock); + return NULL; + } + virObjectUnref(sock); + + if (identity && + virNetServerClientSetIdentity(client, identity) < 0) + goto error; + + if (privNew) { + if (!(child = virJSONValueObjectGet(object, "privateData"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing privateData field in JSON state document")); + goto error; + } + if (!(client->privateData = privNew(client, child, privOpaque))) { + goto error; + } + client->privateDataFreeFunc = privFree; + client->privateDataPreExecRestart = privPreExecRestart; + } + + + return client; + +error: + virObjectUnref(client); + return NULL; +} + + +virJSONValuePtr virNetServerClientPreExecRestart(virNetServerClientPtr client) +{ + virJSONValuePtr object = virJSONValueNewObject(); + virJSONValuePtr child; + + if (!object) + return NULL; + + virNetServerClientLock(client); + + if (virJSONValueObjectAppendNumberInt(object, "auth", client->auth) < 0) + goto error; + if (virJSONValueObjectAppendBoolean(object, "readonly", client->readonly) < 0) + goto error; + if (virJSONValueObjectAppendNumberUint(object, "nrequests_max", client->nrequests_max) < 0) + goto error; + + if (client->identity && + virJSONValueObjectAppendString(object, "identity", client->identity) < 0) + goto error; + + if (!(child = virNetSocketPreExecRestart(client->sock))) + goto error; + + if (virJSONValueObjectAppend(object, "sock", child) < 0) { + virJSONValueFree(child); + goto error; + } + + if (client->privateData && client->privateDataPreExecRestart && + !(child = client->privateDataPreExecRestart(client, client->privateData))) + goto error; + + if (virJSONValueObjectAppend(object, "privateData", child) < 0) { + virJSONValueFree(child); + goto error; + } + + virNetServerClientUnlock(client); + return object; + +error: + virNetServerClientUnlock(client); + virJSONValueFree(object); + return NULL; +} + + int virNetServerClientGetAuth(virNetServerClientPtr client) { int auth; diff --git a/src/rpc/virnetserverclient.h b/src/rpc/virnetserverclient.h index f950c61..2a2413b 100644 --- a/src/rpc/virnetserverclient.h +++ b/src/rpc/virnetserverclient.h @@ -27,6 +27,7 @@ # include "virnetsocket.h" # include "virnetmessage.h" # include "virobject.h" +# include "json.h" typedef struct _virNetServerClient virNetServerClient; typedef virNetServerClient *virNetServerClientPtr; @@ -39,6 +40,11 @@ typedef int (*virNetServerClientFilterFunc)(virNetServerClientPtr client, virNetMessagePtr msg, void *opaque); +typedef virJSONValuePtr (*virNetServerClientPrivPreExecRestart)(virNetServerClientPtr client, + void *data); +typedef void *(*virNetServerClientPrivNewPostExecRestart)(virNetServerClientPtr client, + virJSONValuePtr object, + void *opaque); typedef void *(*virNetServerClientPrivNew)(virNetServerClientPtr client, void *opaque); @@ -48,9 +54,18 @@ virNetServerClientPtr virNetServerClientNew(virNetSocketPtr sock, size_t nrequests_max, virNetTLSContextPtr tls, virNetServerClientPrivNew privNew, + virNetServerClientPrivPreExecRestart privPreExecRestart, virFreeCallback privFree, void *privOpaque); +virNetServerClientPtr virNetServerClientNewPostExecRestart(virJSONValuePtr object, + virNetServerClientPrivNewPostExecRestart privNew, + virNetServerClientPrivPreExecRestart privPreExecRestart, + virFreeCallback privFree, + void *privOpaque); + +virJSONValuePtr virNetServerClientPreExecRestart(virNetServerClientPtr client); + int virNetServerClientAddFilter(virNetServerClientPtr client, virNetServerClientFilterFunc func, void *opaque); -- 1.7.11.2

On 08/09/2012 09:20 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
Add two new APIs virNetServerClientNewPostExecRestart and virNetServerClientPreExecRestart which allow a virNetServerClientPtr object to be created from a JSON object and saved to a JSON object, for the purpose of re-exec'ing a process.
This includes serialization of the connected socket associated with the client
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> ---
+virNetServerClientPtr virNetServerClientNewPostExecRestart(virJSONValuePtr object, + virNetServerClientPrivNewPostExecRestart privNew, + virNetServerClientPrivPreExecRestart privPreExecRestart, + virFreeCallback privFree, + void *privOpaque) +{ + virJSONValuePtr child; + virNetServerClientPtr client = NULL; + virNetSocketPtr sock; + const char *identity = NULL; + int auth; + bool readonly; + unsigned int nrequests_max; + + if (virJSONValueObjectGetNumberInt(object, "auth", &auth) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing auth field in JSON state document")); + return NULL; + } + if (virJSONValueObjectGetBoolean(object, "readonly", &readonly) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing readonly field in JSON state document")); + return NULL; + } + if (virJSONValueObjectGetNumberUint(object, "nrequests_max", + (unsigned int *)&nrequests_max) < 0) {
Why the cast? &nrequests_max is already (unsigned int *). -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

From: "Daniel P. Berrange" <berrange@redhat.com> Add two new APIs virNetServerNewPostExecRestart and virNetServerPreExecRestart which allow a virNetServerPtr object to be created from a JSON object and saved to a JSON object, for the purpose of re-exec'ing a process. This includes serialization of all registered services and clients Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/libvirt_private.syms | 2 + src/rpc/virnetserver.c | 252 +++++++++++++++++++++++++++++++++++++++++++++++ src/rpc/virnetserver.h | 10 ++ 3 files changed, 264 insertions(+) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index cdb9b57..879919f 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1500,6 +1500,8 @@ virNetServerClose; virNetServerIsPrivileged; virNetServerKeepAliveRequired; virNetServerNew; +virNetServerNewPostExecRestart; +virNetServerPreExecRestart; virNetServerQuit; virNetServerRun; virNetServerSetTLSContext; diff --git a/src/rpc/virnetserver.c b/src/rpc/virnetserver.c index 902c34e..92e0264 100644 --- a/src/rpc/virnetserver.c +++ b/src/rpc/virnetserver.c @@ -447,6 +447,258 @@ error: } +virNetServerPtr virNetServerNewPostExecRestart(virJSONValuePtr object, + virNetServerClientPrivNew clientPrivNew, + virNetServerClientPrivNewPostExecRestart clientPrivNewPostExecRestart, + virNetServerClientPrivPreExecRestart clientPrivPreExecRestart, + virFreeCallback clientPrivFree, + void *clientPrivOpaque) +{ + virNetServerPtr srv = NULL; + virJSONValuePtr clients; + virJSONValuePtr services; + size_t i; + int n; + unsigned int min_workers; + unsigned int max_workers; + unsigned int priority_workers; + unsigned int max_clients; + unsigned int keepaliveInterval; + unsigned int keepaliveCount; + bool keepaliveRequired; + const char *mdnsGroupName = NULL; + + if (virJSONValueObjectGetNumberUint(object, "min_workers", &min_workers) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing min_workers data in JSON document")); + goto error; + } + if (virJSONValueObjectGetNumberUint(object, "max_workers", &max_workers) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing max_workers data in JSON document")); + goto error; + } + if (virJSONValueObjectGetNumberUint(object, "priority_workers", &priority_workers) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing priority_workers data in JSON document")); + goto error; + } + if (virJSONValueObjectGetNumberUint(object, "max_clients", &max_clients) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing max_clients data in JSON document")); + goto error; + } + if (virJSONValueObjectGetNumberUint(object, "keepaliveInterval", &keepaliveInterval) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing keepaliveInterval data in JSON document")); + goto error; + } + if (virJSONValueObjectGetNumberUint(object, "keepaliveCount", &keepaliveCount) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing keepaliveCount data in JSON document")); + goto error; + } + if (virJSONValueObjectGetBoolean(object, "keepaliveRequired", &keepaliveRequired) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing keepaliveRequired data in JSON document")); + goto error; + } + + if (virJSONValueObjectHasKey(object, "mdnsGroupName") && + (!(mdnsGroupName = virJSONValueObjectGetString(object, "mdnsGroupName")))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Malformed mdnsGroupName data in JSON document")); + goto error; + } + + if (!(srv = virNetServerNew(min_workers, max_clients, + priority_workers, max_clients, + keepaliveInterval, keepaliveCount, + keepaliveRequired, mdnsGroupName, + clientPrivNew, clientPrivPreExecRestart, + clientPrivFree, clientPrivOpaque))) + goto error; + + if (!(services = virJSONValueObjectGet(object, "services"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing services data in JSON document")); + goto error; + } + + n = virJSONValueArraySize(services); + if (n < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Malformed services data in JSON document")); + goto error; + } + + for (i = 0 ; i < n ; i++) { + virNetServerServicePtr service; + virJSONValuePtr child = virJSONValueArrayGet(services, i); + if (!child) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing service data in JSON document")); + goto error; + } + + if (!(service = virNetServerServiceNewPostExecRestart(child))) + goto error; + + /* XXX mdns entry names ? */ + if (virNetServerAddService(srv, service, NULL) < 0) { + virObjectUnref(service); + goto error; + } + } + + + if (!(clients = virJSONValueObjectGet(object, "clients"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing clients data in JSON document")); + goto error; + } + + n = virJSONValueArraySize(clients); + if (n < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Malformed clients data in JSON document")); + goto error; + } + + for (i = 0 ; i < n ; i++) { + virNetServerClientPtr client; + virJSONValuePtr child = virJSONValueArrayGet(clients, i); + if (!child) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing client data in JSON document")); + goto error; + } + + if (!(client = virNetServerClientNewPostExecRestart(child, + clientPrivNewPostExecRestart, + clientPrivPreExecRestart, + clientPrivFree, + clientPrivOpaque))) + goto error; + + if (virNetServerAddClient(srv, client) < 0) { + virObjectUnref(client); + goto error; + } + virObjectUnref(client); + } + + return srv; + +error: + virObjectUnref(srv); + return NULL; +} + + +virJSONValuePtr virNetServerPreExecRestart(virNetServerPtr srv) +{ + virJSONValuePtr object; + virJSONValuePtr clients; + virJSONValuePtr services; + size_t i; + + virMutexLock(&srv->lock); + + if (!(object = virJSONValueNewObject())) + goto error; + + if (virJSONValueObjectAppendNumberUint(object, "min_workers", + virThreadPoolGetMinWorkers(srv->workers)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Cannot set min_workers data in JSON document")); + goto error; + } + if (virJSONValueObjectAppendNumberUint(object, "max_workers", + virThreadPoolGetMaxWorkers(srv->workers)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Cannot set max_workers data in JSON document")); + goto error; + } + if (virJSONValueObjectAppendNumberUint(object, "priority_workers", + virThreadPoolGetPriorityWorkers(srv->workers)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Cannot set priority_workers data in JSON document")); + goto error; + } + if (virJSONValueObjectAppendNumberUint(object, "max_clients", srv->nclients_max) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Cannot set max_clients data in JSON document")); + goto error; + } + if (virJSONValueObjectAppendNumberUint(object, "keepaliveInterval", srv->keepaliveInterval) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Cannot set keepaliveInterval data in JSON document")); + goto error; + } + if (virJSONValueObjectAppendNumberUint(object, "keepaliveCount", srv->keepaliveCount) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Cannot set keepaliveCount data in JSON document")); + goto error; + } + if (virJSONValueObjectAppendBoolean(object, "keepaliveRequired", srv->keepaliveRequired) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Cannot set keepaliveRequired data in JSON document")); + goto error; + } + + if (srv->mdnsGroupName && + virJSONValueObjectAppendString(object, "mdnsGroupName", srv->mdnsGroupName) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Cannot set mdnsGroupName data in JSON document")); + goto error; + } + + services = virJSONValueNewArray(); + if (virJSONValueObjectAppend(object, "services", services) < 0) { + virJSONValueFree(services); + goto error; + } + + for (i = 0 ; i < srv->nservices ; i++) { + virJSONValuePtr child; + if (!(child = virNetServerServicePreExecRestart(srv->services[i]))) + goto error; + + if (virJSONValueArrayAppend(services, child) < 0) { + virJSONValueFree(child); + goto error; + } + } + + clients = virJSONValueNewArray(); + if (virJSONValueObjectAppend(object, "clients", clients) < 0) { + virJSONValueFree(clients); + goto error; + } + + for (i = 0 ; i < srv->nclients ; i++) { + virJSONValuePtr child; + if (!(child = virNetServerClientPreExecRestart(srv->clients[i]))) + goto error; + + if (virJSONValueArrayAppend(clients, child) < 0) { + virJSONValueFree(child); + goto error; + } + } + + virMutexUnlock(&srv->lock); + + return object; + +error: + virJSONValueFree(object); + virMutexUnlock(&srv->lock); + return NULL; +} + + bool virNetServerIsPrivileged(virNetServerPtr srv) { bool priv; diff --git a/src/rpc/virnetserver.h b/src/rpc/virnetserver.h index 778b069..a5cbcd3 100644 --- a/src/rpc/virnetserver.h +++ b/src/rpc/virnetserver.h @@ -31,6 +31,7 @@ # include "virnetserverclient.h" # include "virnetserverservice.h" # include "virobject.h" +# include "json.h" virNetServerPtr virNetServerNew(size_t min_workers, size_t max_workers, @@ -45,6 +46,15 @@ virNetServerPtr virNetServerNew(size_t min_workers, virFreeCallback clientPrivFree, void *clientPrivOpaque); +virNetServerPtr virNetServerNewPostExecRestart(virJSONValuePtr object, + virNetServerClientPrivNew clientPrivNew, + virNetServerClientPrivNewPostExecRestart clientPrivNewPostExecRestart, + virNetServerClientPrivPreExecRestart clientPrivPreExecRestart, + virFreeCallback clientPrivFree, + void *clientPrivOpaque); + +virJSONValuePtr virNetServerPreExecRestart(virNetServerPtr srv); + typedef int (*virNetServerAutoShutdownFunc)(virNetServerPtr srv, void *opaque); bool virNetServerIsPrivileged(virNetServerPtr srv); -- 1.7.11.2

On 08/09/2012 09:20 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
Add two new APIs virNetServerNewPostExecRestart and virNetServerPreExecRestart which allow a virNetServerPtr object to be created from a JSON object and saved to a JSON object, for the purpose of re-exec'ing a process.
This includes serialization of all registered services and clients
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/libvirt_private.syms | 2 + src/rpc/virnetserver.c | 252 +++++++++++++++++++++++++++++++++++++++++++++++ src/rpc/virnetserver.h | 10 ++ 3 files changed, 264 insertions(+)
Looks reasonable, but may be impacted by any changes to earlier patches. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

From: "Daniel P. Berrange" <berrange@redhat.com> The virtlockd daemon will maintain locks on behalf of libvirtd. There are two reasons for it to be separate - Avoid risk of other libvirtd threads accidentally releasing fcntl() locks by opening + closing a file that is locked - Ensure locks can be preserved across libvirtd restarts. virtlockd will need to be able to re-exec itself while maintaining locks. This is simpler to achieve if its sole job is maintaining locks Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- .gitignore | 2 + cfg.mk | 6 +- libvirt.spec.in | 7 + po/POTFILES.in | 1 + src/Makefile.am | 86 ++++- src/locking/lock_daemon.c | 750 ++++++++++++++++++++++++++++++++++++++++++ src/locking/lock_daemon.h | 43 +++ src/locking/virtlockd.init.in | 93 ++++++ src/locking/virtlockd.sysconf | 3 + 9 files changed, 987 insertions(+), 4 deletions(-) create mode 100644 src/locking/lock_daemon.c create mode 100644 src/locking/lock_daemon.h create mode 100644 src/locking/virtlockd.init.in create mode 100644 src/locking/virtlockd.sysconf diff --git a/.gitignore b/.gitignore index 37767e0..737a7fb 100644 --- a/.gitignore +++ b/.gitignore @@ -117,6 +117,8 @@ /src/test_libvirt*.aug /src/util/virkeymaps.h /src/virt-aa-helper +/src/virtlockd +/src/virtlockd.init /tests/*.log /tests/*.pid /tests/*xml2*test diff --git a/cfg.mk b/cfg.mk index c0457e7..bf3d04d 100644 --- a/cfg.mk +++ b/cfg.mk @@ -636,6 +636,8 @@ sc_prohibit_cross_inclusion: @for dir in $(cross_dirs); do \ case $$dir in \ util/) safe="util";; \ + locking/) \ + safe="($$dir|util|conf|rpc)";; \ cpu/ | locking/ | network/ | rpc/ | security/) \ safe="($$dir|util|conf)";; \ xenapi/ | xenxs/ ) safe="($$dir|util|conf|xen)";; \ @@ -729,7 +731,7 @@ $(srcdir)/src/remote/remote_client_bodies.h: $(srcdir)/src/remote/remote_protoco # List all syntax-check exemptions: exclude_file_name_regexp--sc_avoid_strcase = ^tools/virsh(-domain-monitor|-snapshot)?\.c$$ -_src1=libvirt|fdstream|qemu/qemu_monitor|util/(command|util)|xen/xend_internal|rpc/virnetsocket|lxc/lxc_controller +_src1=libvirt|fdstream|qemu/qemu_monitor|util/(command|util)|xen/xend_internal|rpc/virnetsocket|lxc/lxc_controller|locking/lock_daemon exclude_file_name_regexp--sc_avoid_write = \ ^(src/($(_src1))|daemon/libvirtd|tools/console|tests/(shunload|virnettlscontext)test)\.c$$ @@ -762,7 +764,7 @@ exclude_file_name_regexp--sc_prohibit_close = \ exclude_file_name_regexp--sc_prohibit_empty_lines_at_EOF = \ (^tests/(qemuhelp|nodeinfo)data/|\.(gif|ico|png|diff)$$) -_src2=src/(util/command|libvirt|lxc/lxc_controller) +_src2=src/(util/command|libvirt|lxc/lxc_controller|locking/lock_daemon) exclude_file_name_regexp--sc_prohibit_fork_wrappers = \ (^($(_src2)|tests/testutils|daemon/libvirtd)\.c$$) diff --git a/libvirt.spec.in b/libvirt.spec.in index 67b955a..1cbeca2 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -1528,11 +1528,13 @@ fi %dir %attr(0700, root, root) %{_sysconfdir}/libvirt/nwfilter/ %{_sysconfdir}/rc.d/init.d/libvirtd +%{_sysconfdir}/rc.d/init.d/virtlockd %if %{with_systemd} %{_unitdir}/libvirtd.service %endif %doc daemon/libvirtd.upstart %config(noreplace) %{_sysconfdir}/sysconfig/libvirtd +%config(noreplace) %{_sysconfdir}/sysconfig/virtlockd %config(noreplace) %{_sysconfdir}/libvirt/libvirtd.conf %if 0%{?fedora} >= 14 || 0%{?rhel} >= 6 %config(noreplace) %{_sysconfdir}/sysctl.d/libvirtd @@ -1596,6 +1598,10 @@ rm -f $RPM_BUILD_ROOT%{_sysconfdir}/sysctl.d/libvirtd %dir %attr(0755, root, root) %{_localstatedir}/lib/libvirt/dnsmasq/ %endif +%if %{with_libvirtd} +%dir %attr(0755, root, root) %{_libdir}/libvirt/lock-driver +%endif + %if %{with_qemu} %{_datadir}/augeas/lenses/libvirtd_qemu.aug %{_datadir}/augeas/lenses/tests/test_libvirtd_qemu.aug @@ -1629,6 +1635,7 @@ rm -f $RPM_BUILD_ROOT%{_sysconfdir}/sysctl.d/libvirtd %attr(0755, root, root) %{_libexecdir}/libvirt_iohelper %attr(0755, root, root) %{_sbindir}/libvirtd +%attr(0755, root, root) %{_sbindir}/virtlockd %{_mandir}/man8/libvirtd.8* diff --git a/po/POTFILES.in b/po/POTFILES.in index c8dbe4b..992c77d 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -40,6 +40,7 @@ src/interface/netcf_driver.c src/internal.h src/libvirt.c src/libvirt-qemu.c +src/locking/lock_daemon.c src/locking/lock_driver_sanlock.c src/locking/lock_manager.c src/lxc/lxc_cgroup.c diff --git a/src/Makefile.am b/src/Makefile.am index 81ed279..3e960a8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -140,6 +140,10 @@ DRIVER_SOURCES = \ LOCK_DRIVER_SANLOCK_SOURCES = \ locking/lock_driver_sanlock.c +LOCK_DAEMON_SOURCES = \ + locking/lock_daemon.h \ + locking/lock_daemon.c \ + $(NULL) NETDEV_CONF_SOURCES = \ conf/netdev_bandwidth_conf.h conf/netdev_bandwidth_conf.c \ @@ -1452,6 +1456,76 @@ libvirt_qemu_la_CFLAGS = $(AM_CFLAGS) libvirt_qemu_la_LIBADD = libvirt.la $(CYGWIN_EXTRA_LIBADD) EXTRA_DIST += $(LIBVIRT_QEMU_SYMBOL_FILE) +if WITH_LIBVIRTD +sbin_PROGRAMS = virtlockd + +virtlockd_SOURCES = $(LOCK_DAEMON_SOURCES) +virtlockd_CFLAGS = \ + $(AM_CFLAGS) \ + $(NULL) +virtlockd_LDFLAGS = \ + $(AM_LDFLAGS) \ + $(CYGWIN_EXTRA_LDFLAGS) \ + $(MINGW_EXTRA_LDFLAGS) \ + $(NULL) +virtlockd_LDADD = \ + libvirt-net-rpc-server.la \ + libvirt-net-rpc.la \ + libvirt_util.la \ + ../gnulib/lib/libgnu.la \ + $(CYGWIN_EXTRA_LIBADD) \ + $(NULL) +if WITH_DTRACE_PROBES +virtlockd_LDADD += libvirt_probes.lo +endif + +else +EXTRA_DIST += $(LOCK_DAEMON_SOURCES) +endif + +EXTRA_DIST += locking/virtlockd.sysconf + +install-sysconfig: + mkdir -p $(DESTDIR)$(sysconfdir)/sysconfig + $(INSTALL_DATA) $(srcdir)/locking/virtlockd.sysconf \ + $(DESTDIR)$(sysconfdir)/sysconfig/virtlockd + +uninstall-sysconfig: + rm -f $(DESTDIR)$(sysconfdir)/sysconfig/virtlockd + +EXTRA_DIST += locking/virtlockd.init.in + +if WITH_LIBVIRTD +if LIBVIRT_INIT_SCRIPT_RED_HAT +install-init:: virtlockd.init install-sysconfig + mkdir -p $(DESTDIR)$(sysconfdir)/rc.d/init.d + $(INSTALL_SCRIPT) virtlockd.init \ + $(DESTDIR)$(sysconfdir)/rc.d/init.d/virtlockd + +uninstall-init:: uninstall-sysconfig + rm -f $(DESTDIR)$(sysconfdir)/rc.d/init.d/libvirtd + +BUILT_SOURCES += virtlockd.init +else +install-init:: +uninstall-init:: +endif +else +install-init:: +uninstall-init:: +endif + +virtlockd.init: locking/virtlockd.init.in $(top_builddir)/config.status + $(AM_V_GEN)sed \ + -e "s!::localstatedir::!$(localstatedir)!g" \ + -e "s!::sbindir::!$(sbindir)!g" \ + -e "s!::sysconfdir::!$(sysconfdir)!g" \ + < $< > $@-t && \ + chmod a+x $@-t && \ + mv $@-t $@ + + + if HAVE_SANLOCK lockdriverdir = $(libdir)/libvirt/lock-driver lockdriver_LTLIBRARIES = sanlock.la @@ -1659,7 +1733,11 @@ endif endif EXTRA_DIST += $(SECURITY_DRIVER_APPARMOR_HELPER_SOURCES) -install-data-local: +install-data-local: install-init +if WITH_LIBVIRTD + $(MKDIR_P) "$(DESTDIR)$(localstatedir)/lib/libvirt/lockd" + $(MKDIR_P) "$(DESTDIR)$(localstatedir)/run/libvirt/lockd" +endif $(MKDIR_P) "$(DESTDIR)$(localstatedir)/cache/libvirt" $(MKDIR_P) "$(DESTDIR)$(localstatedir)/lib/libvirt/images" $(MKDIR_P) "$(DESTDIR)$(localstatedir)/lib/libvirt/filesystems" @@ -1705,7 +1783,11 @@ if WITH_NETWORK $(DESTDIR)$(sysconfdir)/libvirt/qemu/networks/autostart/default.xml endif -uninstall-local:: +uninstall-local:: uninstall-init +if WITH_LIBVIRTD + rmdir "$(DESTDIR)$(localstatedir)/lib/libvirt/lockd" ||: + rmdir "$(DESTDIR)$(localstatedir)/run/libvirt/lockd" ||: +endif rmdir "$(DESTDIR)$(localstatedir)/cache/libvirt" ||: rmdir "$(DESTDIR)$(localstatedir)/lib/libvirt/images" ||: rmdir "$(DESTDIR)$(localstatedir)/lib/libvirt/filesystems" ||: diff --git a/src/locking/lock_daemon.c b/src/locking/lock_daemon.c new file mode 100644 index 0000000..287ad8c --- /dev/null +++ b/src/locking/lock_daemon.c @@ -0,0 +1,750 @@ +/* + * lock_daemon.c: lock management daemon + * + * Copyright (C) 2006-2012 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/>. + * + * Author: Daniel P. Berrange <berrange@redhat.com> + */ + +#include <config.h> + +#include <unistd.h> +#include <fcntl.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <getopt.h> +#include <stdlib.h> +#include <locale.h> + + +#include "lock_daemon.h" +#include "util.h" +#include "virfile.h" +#include "virpidfile.h" +#include "virterror_internal.h" +#include "logging.h" +#include "memory.h" +#include "conf.h" +#include "rpc/virnetserver.h" +#include "virrandom.h" +#include "virhash.h" + +#include "configmake.h" + +#define VIR_FROM_THIS VIR_FROM_LOCKING + +#define virLockError(code, ...) \ + virReportErrorHelper(VIR_FROM_THIS, code, __FILE__, \ + __FUNCTION__, __LINE__, __VA_ARGS__) + +struct _virLockDaemon { + virMutex lock; + virNetServerPtr srv; +}; + +virLockDaemonPtr lockDaemon = NULL; + +static bool privileged; + +enum { + VIR_LOCK_DAEMON_ERR_NONE = 0, + VIR_LOCK_DAEMON_ERR_PIDFILE, + VIR_LOCK_DAEMON_ERR_RUNDIR, + VIR_LOCK_DAEMON_ERR_INIT, + VIR_LOCK_DAEMON_ERR_SIGNAL, + VIR_LOCK_DAEMON_ERR_PRIVS, + VIR_LOCK_DAEMON_ERR_NETWORK, + VIR_LOCK_DAEMON_ERR_CONFIG, + VIR_LOCK_DAEMON_ERR_HOOKS, + + VIR_LOCK_DAEMON_ERR_LAST +}; + +VIR_ENUM_DECL(virDaemonErr) +VIR_ENUM_IMPL(virDaemonErr, VIR_LOCK_DAEMON_ERR_LAST, + "Initialization successful", + "Unable to obtain pidfile", + "Unable to create rundir", + "Unable to initialize libvirt", + "Unable to setup signal handlers", + "Unable to drop privileges", + "Unable to initialize network sockets", + "Unable to load configuration file", + "Unable to look for hook scripts"); + +static void * +virLockDaemonClientNew(virNetServerClientPtr client, + void *opaque); +static void +virLockDaemonClientFree(void *opaque); + +static void +virLockDaemonFree(virLockDaemonPtr lockd) +{ + if (!lockd) + return; + + virObjectUnref(lockd->srv); + + VIR_FREE(lockd); +} + + +static virLockDaemonPtr +virLockDaemonNew(void) +{ + virLockDaemonPtr lockd; + + if (VIR_ALLOC(lockd) < 0) { + virReportOOMError(); + return NULL; + } + + if (virMutexInit(&lockd->lock) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Unable to initialize mutex")); + VIR_FREE(lockd); + return NULL; + } + + if (!(lockd->srv = virNetServerNew(1, 1, 0, 20, + -1, 0, + NULL, NULL, + virLockDaemonClientNew, + NULL, + virLockDaemonClientFree, + NULL))) + goto error; + + return lockd; + +error: + virLockDaemonFree(lockd); + return NULL; +} + + +static int +virLockDaemonForkIntoBackground(const char *argv0) +{ + int statuspipe[2]; + if (pipe(statuspipe) < 0) + return -1; + + pid_t pid = fork(); + switch (pid) { + case 0: + { + int stdinfd = -1; + int stdoutfd = -1; + int nextpid; + + VIR_FORCE_CLOSE(statuspipe[0]); + + if ((stdinfd = open("/dev/null", O_RDONLY)) < 0) + goto cleanup; + if ((stdoutfd = open("/dev/null", O_WRONLY)) < 0) + goto cleanup; + if (dup2(stdinfd, STDIN_FILENO) != STDIN_FILENO) + goto cleanup; + if (dup2(stdoutfd, STDOUT_FILENO) != STDOUT_FILENO) + goto cleanup; + if (dup2(stdoutfd, STDERR_FILENO) != STDERR_FILENO) + goto cleanup; + if (VIR_CLOSE(stdinfd) < 0) + goto cleanup; + if (VIR_CLOSE(stdoutfd) < 0) + goto cleanup; + + if (setsid() < 0) + goto cleanup; + + nextpid = fork(); + switch (nextpid) { + case 0: + return statuspipe[1]; + case -1: + return -1; + default: + _exit(0); + } + + cleanup: + VIR_FORCE_CLOSE(stdoutfd); + VIR_FORCE_CLOSE(stdinfd); + return -1; + + } + + case -1: + return -1; + + default: + { + int got, exitstatus = 0; + int ret; + char status; + + VIR_FORCE_CLOSE(statuspipe[1]); + + /* We wait to make sure the first child forked successfully */ + if ((got = waitpid(pid, &exitstatus, 0)) < 0 || + got != pid || + exitstatus != 0) { + return -1; + } + + /* Now block until the second child initializes successfully */ + again: + ret = read(statuspipe[0], &status, 1); + if (ret == -1 && errno == EINTR) + goto again; + + if (ret == 1 && status != 0) { + fprintf(stderr, + _("%s: error: %s. Check /var/log/messages or run without " + "--daemon for more info.\n"), argv0, + virDaemonErrTypeToString(status)); + } + _exit(ret == 1 && status == 0 ? 0 : 1); + } + } +} + + +static int +virLockDaemonMakePaths(char **basedir, + char **statedir, + char **pidfile, + char **sockfile) +{ + char *userdir = NULL; + + *basedir = *statedir = *pidfile = *sockfile = NULL; + + if (privileged) { + if (!(*basedir = strdup(LOCALSTATEDIR "/run/libvirt"))) + goto no_memory; + } else { + if (!(userdir = virGetUserDirectory())) + goto error; + + if (virAsprintf(basedir, "%s/.libvirt", userdir) < 0) + goto no_memory; + } + + if (virAsprintf(statedir, "%s/virtlockd", *basedir) < 0) + goto no_memory; + if (virAsprintf(pidfile, "%s/virtlockd.pid", *statedir) < 0) + goto no_memory; + if (virAsprintf(sockfile, "%s/virtlockd.sock", *statedir) < 0) + goto no_memory; + + VIR_FREE(userdir); + return 0; + +no_memory: + VIR_FREE(*basedir); + VIR_FREE(*statedir); + VIR_FREE(*pidfile); + VIR_FREE(*sockfile); +error: + VIR_FREE(userdir); + return -1; +} + +static void +virLockDaemonErrorHandler(void *opaque ATTRIBUTE_UNUSED, + virErrorPtr err ATTRIBUTE_UNUSED) +{ + /* Don't do anything, since logging infrastructure already + * took care of reporting the error */ +} + + +/* + * Set up the logging environment + * By default if daemonized all errors go to syslog and the logging + * is also saved onto the logfile libvird.log, but if verbose or error + * debugging is asked for then output informations or debug. + */ +static int +virLockDaemonSetLogging(virConfPtr conf ATTRIBUTE_UNUSED, + const char *filename ATTRIBUTE_UNUSED, + int godaemon, int verbose) +{ + //int log_level = 0; + char *log_filters = NULL; + char *log_outputs = NULL; + int ret = -1; + + virLogReset(); +#if 0 + /* + * Libvirtd's order of precedence is: + * cmdline > environment > config + * + * In order to achieve this, we must process configuration in + * different order for the log level versus the filters and + * outputs. Because filters and outputs append, we have to look at + * the environment first and then only check the config file if + * there was no result from the environment. The default output is + * then applied only if there was no setting from either of the + * first two. Because we don't have a way to determine if the log + * level has been set, we must process variables in the opposite + * order, each one overriding the previous. + */ + GET_CONF_INT (conf, filename, log_level); + if (log_level != 0) + virLogSetDefaultPriority(log_level); + + if (virLogGetNbFilters() == 0) { + GET_CONF_STR (conf, filename, log_filters); + virLogParseFilters(log_filters); + } + + if (virLogGetNbOutputs() == 0) { + GET_CONF_STR (conf, filename, log_outputs); + virLogParseOutputs(log_outputs); + } +#endif + + virLogSetFromEnv(); + + /* + * If no defined outputs, then direct to syslog when running + * as daemon. Otherwise the default output is stderr. + */ + if (virLogGetNbOutputs() == 0) { + char *tmp = NULL; + if (godaemon) { + if (virAsprintf (&tmp, "%d:syslog:libvirtd", + virLogGetDefaultPriority()) < 0) + goto free_and_fail; + } else { + if (virAsprintf (&tmp, "%d:stderr", + virLogGetDefaultPriority()) < 0) + goto free_and_fail; + } + virLogParseOutputs(tmp); + VIR_FREE(tmp); + } + + /* + * Command line override for --verbose + */ + if ((verbose) && (virLogGetDefaultPriority() > VIR_LOG_INFO)) + virLogSetDefaultPriority(VIR_LOG_INFO); + + ret = 0; + +free_and_fail: + VIR_FREE(log_filters); + VIR_FREE(log_outputs); + return ret; +} + +/* Read the config file if it exists. + * Only used in the remote case, hence the name. + */ +static int +virLockDaemonReadConfigFile(const char *filename, + int godaemon, int verbose) +{ + virConfPtr conf; + + if (!(conf = virConfReadFile (filename, 0))) + goto error; + + if (virLockDaemonSetLogging(conf, filename, godaemon, verbose) < 0) + goto error; + + virConfFree (conf); + return 0; + +error: + virConfFree (conf); + + return -1; +} + +/* Display version information. */ +static void + virLockDaemonVersion(const char *argv0) +{ + printf ("%s (%s) %s\n", argv0, PACKAGE_NAME, PACKAGE_VERSION); +} + +static void +virLockDaemonShutdownHandler(virNetServerPtr srv, + siginfo_t *sig ATTRIBUTE_UNUSED, + void *opaque ATTRIBUTE_UNUSED) +{ + virNetServerQuit(srv); +} + +static int +virLockDaemonSetupSignals(virNetServerPtr srv) +{ + if (virNetServerAddSignalHandler(srv, SIGINT, virLockDaemonShutdownHandler, NULL) < 0) + return -1; + if (virNetServerAddSignalHandler(srv, SIGQUIT, virLockDaemonShutdownHandler, NULL) < 0) + return -1; + if (virNetServerAddSignalHandler(srv, SIGTERM, virLockDaemonShutdownHandler, NULL) < 0) + return -1; + return 0; +} + +static int +virLockDaemonSetupNetworking(virNetServerPtr srv, const char *sock_path) +{ + virNetServerServicePtr svc; + + VIR_DEBUG("Setting up networking natively"); + + if (!(svc = virNetServerServiceNewUNIX(sock_path, 0700, 0, 0, false, 1, NULL))) + return -1; + + if (virNetServerAddService(srv, svc, NULL) < 0) { + virObjectUnref(svc); + return -1; + } + return 0; +} + + +static void +virLockDaemonClientFree(void *opaque) +{ + virLockDaemonClientPtr priv = opaque; + + if (!priv) + return; + + VIR_DEBUG("priv=%p client=%lld", + priv, + (unsigned long long)priv->clientPid); + + virMutexDestroy(&priv->lock); + VIR_FREE(priv); +} + + +static void * +virLockDaemonClientNew(virNetServerClientPtr client, + void *opaque ATTRIBUTE_UNUSED) +{ + virLockDaemonClientPtr priv; + uid_t clientuid; + gid_t clientgid; + + if (VIR_ALLOC(priv) < 0) { + virReportOOMError(); + return NULL; + } + + if (virMutexInit(&priv->lock) < 0) { + VIR_FREE(priv); + virReportOOMError(); + return NULL; + } + + if (virNetServerClientGetUNIXIdentity(client, + &clientuid, + &clientgid, + &priv->clientPid) < 0) + goto error; + + VIR_DEBUG("New client pid %llu uid %llu", + (unsigned long long)priv->clientPid, + (unsigned long long)clientuid); + + if (!privileged) { + if (geteuid() != clientuid) { + virLockError(VIR_ERR_OPERATION_DENIED, + _("Disallowing client %llu with uid %llu"), + (unsigned long long)priv->clientPid, + (unsigned long long)clientuid); + + goto error; + } + } else { + if (clientuid != 0) { + virLockError(VIR_ERR_OPERATION_DENIED, + _("Disallowing client %llu with uid %llu"), + (unsigned long long)priv->clientPid, + (unsigned long long)clientuid); + goto error; + } + } + + return priv; + +error: + virMutexDestroy(&priv->lock); + VIR_FREE(priv); + return NULL; +} + + +static void +virLockDaemonUsage(const char *argv0) +{ + fprintf (stderr, + _("\n\ +Usage:\n\ + %s [options]\n\ +\n\ +Options:\n\ + -v | --verbose Verbose messages.\n\ + -d | --daemon Run as a daemon & write PID file.\n\ + -t | --timeout <secs> Exit after timeout period.\n\ + -f | --config <file> Configuration file.\n\ + | --version Display version information.\n\ + -p | --pid-file <file> Change name of PID file.\n\ +\n\ +libvirt lock management daemon:\n\ +\n\ + Default paths:\n\ +\n\ + Configuration file (unless overridden by -f):\n\ + %s/libvirt/libvirtd.conf\n\ +\n\ + Sockets (as root):\n\ + %s/run/virtlockd/virtlockd.sock\n\ +\n\ + Sockets (as non-root):\n\ + $HOME/.libvirt/virtlockd/virtlockd.sock (in UNIX abstract namespace)\n\ +\n\ + Default PID file (as root):\ + %s/run/vitlockd/virtlockd.pid\n\ +\n\ + Default PID file (as non-root):\ + $HOME/.libvirt/virtlockd/virtlockd.pid\n\ +\n"), + argv0, + SYSCONFDIR, + LOCALSTATEDIR, + LOCALSTATEDIR); +} + +enum { + OPT_VERSION = 129 +}; + +#define MAX_LISTEN 5 +int main(int argc, char **argv) { + const char *remote_config_file = NULL; + int statuswrite = -1; + int ret = 1; + int verbose = 0; + int godaemon = 0; + int timeout = 0; + char *base_dir = NULL; + char *state_dir = NULL; + char *pid_file = NULL; + int pid_file_fd = -1; + char *sock_file = NULL; + + struct option opts[] = { + { "verbose", no_argument, &verbose, 1}, + { "daemon", no_argument, &godaemon, 1}, + { "config", required_argument, NULL, 'f'}, + { "timeout", required_argument, NULL, 't'}, + { "pid-file", required_argument, NULL, 'p'}, + { "version", no_argument, NULL, OPT_VERSION }, + { "help", no_argument, NULL, '?' }, + {0, 0, 0, 0} + }; + + privileged = getuid() == 0; + + if (setlocale (LC_ALL, "") == NULL || + bindtextdomain (PACKAGE, LOCALEDIR) == NULL || + textdomain(PACKAGE) == NULL || + virThreadInitialize() < 0 || + virErrorInitialize() < 0) { + fprintf(stderr, _("%s: initialization failed\n"), argv[0]); + exit(EXIT_FAILURE); + } + + if (virLockDaemonMakePaths(&base_dir, &state_dir, + &pid_file, &sock_file) < 0) + exit(EXIT_FAILURE); + + while (1) { + int optidx = 0; + int c; + char *tmp; + + c = getopt_long(argc, argv, "ldf:p:t:v", opts, &optidx); + + if (c == -1) { + break; + } + + switch (c) { + case 0: + /* Got one of the flags */ + break; + case 'v': + verbose = 1; + break; + case 'd': + godaemon = 1; + break; + + case 't': + if (virStrToLong_i(optarg, &tmp, 10, &timeout) != 0 + || timeout <= 0 + /* Ensure that we can multiply by 1000 without overflowing. */ + || timeout > INT_MAX / 1000) + timeout = -1; + break; + + case 'p': + VIR_FREE(pid_file); + if (!(pid_file = strdup(optarg))) + exit(EXIT_FAILURE); + break; + + case 'f': + remote_config_file = optarg; + break; + + case OPT_VERSION: + virLockDaemonVersion(argv[0]); + return 0; + + case '?': + virLockDaemonUsage(argv[0]); + return 2; + + default: + fprintf (stderr, _("%s: internal error: unknown flag: %c\n"), + argv[0], c); + exit (EXIT_FAILURE); + } + } + + if (remote_config_file == NULL) { + static const char *default_config_file + = SYSCONFDIR "/libvirt/virtlockd.conf"; + remote_config_file = + (access(default_config_file, R_OK) == 0 + ? default_config_file + : "/dev/null"); + } + + if (godaemon) { + char ebuf[1024]; + if ((statuswrite = virLockDaemonForkIntoBackground(argv[0])) < 0) { + VIR_ERROR(_("Failed to fork as daemon: %s"), + virStrerror(errno, ebuf, sizeof(ebuf))); + goto cleanup; + } + } + + /* Ensure the rundir exists (on tmpfs on some systems) */ + if (mkdir(base_dir, 0755)) { + if (errno != EEXIST) { + char ebuf[1024]; + VIR_ERROR(_("unable to create rundir %s: %s"), base_dir, + virStrerror(errno, ebuf, sizeof(ebuf))); + ret = VIR_LOCK_DAEMON_ERR_RUNDIR; + goto cleanup; + } + } + if (mkdir(state_dir, 0700)) { + if (errno != EEXIST) { + char ebuf[1024]; + VIR_ERROR(_("unable to create rundir %s: %s"), state_dir, + virStrerror(errno, ebuf, sizeof(ebuf))); + ret = VIR_LOCK_DAEMON_ERR_RUNDIR; + goto cleanup; + } + } + + /* If we have a pidfile set, claim it now, exiting if already taken */ + if ((pid_file_fd = virPidFileAcquirePath(pid_file, getpid())) < 0) { + ret = VIR_LOCK_DAEMON_ERR_PIDFILE; + goto cleanup; + } + + /* Read the config file (if it exists). */ + if (virLockDaemonReadConfigFile(remote_config_file, godaemon, verbose) < 0) { + ret = VIR_LOCK_DAEMON_ERR_CONFIG; + goto cleanup; + } + + + if (!(lockDaemon = virLockDaemonNew())) { + ret = VIR_LOCK_DAEMON_ERR_INIT; + goto cleanup; + } + + if (virLockDaemonSetupNetworking(lockDaemon->srv, sock_file) < 0) { + ret = VIR_LOCK_DAEMON_ERR_NETWORK; + goto cleanup; + } + + if ((virLockDaemonSetupSignals(lockDaemon->srv)) < 0) { + ret = VIR_LOCK_DAEMON_ERR_SIGNAL; + goto cleanup; + } + + /* Disable error func, now logging is setup */ + virSetErrorFunc(NULL, virLockDaemonErrorHandler); + + + /* Tell parent of daemon that basic initialization is complete + * In particular we're ready to accept net connections & have + * written the pidfile + */ + if (statuswrite != -1) { + char status = 0; + while (write(statuswrite, &status, 1) == -1 && + errno == EINTR) + ; + VIR_FORCE_CLOSE(statuswrite); + } + + /* Start accepting new clients from network */ + + virNetServerUpdateServices(lockDaemon->srv, true); + virNetServerRun(lockDaemon->srv); + ret = 0; + +cleanup: + virLockDaemonFree(lockDaemon); + if (statuswrite != -1) { + if (ret != 0) { + /* Tell parent of daemon what failed */ + char status = ret; + while (write(statuswrite, &status, 1) == -1 && + errno == EINTR) + ; + } + VIR_FORCE_CLOSE(statuswrite); + } + if (pid_file_fd != -1) + virPidFileReleasePath(pid_file, pid_file_fd); + VIR_FREE(pid_file); + VIR_FREE(sock_file); + VIR_FREE(state_dir); + return ret; +} diff --git a/src/locking/lock_daemon.h b/src/locking/lock_daemon.h new file mode 100644 index 0000000..7bc8c2e --- /dev/null +++ b/src/locking/lock_daemon.h @@ -0,0 +1,43 @@ +/* + * lock_daemon.h: lock management daemon + * + * Copyright (C) 2006-2012 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/>. + * + * Author: Daniel P. Berrange <berrange@redhat.com> + */ + +#ifndef __VIR_LOCK_DAEMON_H__ +# define __VIR_LOCK_DAEMON_H__ + +# include "virlockspace.h" +# include "threads.h" + +typedef struct _virLockDaemon virLockDaemon; +typedef virLockDaemon *virLockDaemonPtr; + +typedef struct _virLockDaemonClient virLockDaemonClient; +typedef virLockDaemonClient *virLockDaemonClientPtr; + +struct _virLockDaemonClient { + virMutex lock; + + pid_t clientPid; +}; + +extern virLockDaemonPtr lockDaemon; + +#endif /* __VIR_LOCK_DAEMON_H__ */ diff --git a/src/locking/virtlockd.init.in b/src/locking/virtlockd.init.in new file mode 100644 index 0000000..e55cbf9 --- /dev/null +++ b/src/locking/virtlockd.init.in @@ -0,0 +1,93 @@ +#!/bin/sh + +# the following is the LSB init header see +# http://www.linux-foundation.org/spec//booksets/LSB-Core-generic/LSB-Core-gen... +# +### BEGIN INIT INFO +# Provides: virtlockd +# Default-Start: 3 4 5 +# Short-Description: virtual machine lock manager +# Description: This is a daemon for managing locks +# on virtual machine disk images +### END INIT INFO + +# the following is chkconfig init header +# +# virtlockd: virtual machine lock manager +# +# chkconfig: 345 97 03 +# description: This is a daemon for managing locks \ +# on virtual machine disk images +# +# processname: virtlockd +# pidfile: ::localstatedir::/run/libvirt/virtlockd.pid +# + +# Source function library. +. ::sysconfdir::/rc.d/init.d/functions + +SERVICE=virtlockd +PROCESS=virtlockd +PIDFILE=::localstatedir::/run/libvirt/lockd/$SERVICE.pid + +VIRTLOCKD_ARGS= + +test -f ::sysconfdir::/sysconfig/virtlockd && . ::sysconfdir::/sysconfig/virtlockd + +RETVAL=0 + +start() { + echo -n $"Starting $SERVICE daemon: " + daemon --pidfile $PIDFILE --check $SERVICE $PROCESS --daemon $VIRTLOCKD_ARGS + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && touch ::localstatedir::/lock/subsys/$SERVICE +} + +stop() { + echo -n $"Stopping $SERVICE daemon: " + + killproc -p $PIDFILE $PROCESS + RETVAL=$? + echo + if [ $RETVAL -eq 0 ]; then + rm -f ::localstatedir::/lock/subsys/$SERVICE + rm -f $PIDFILE + fi +} + +restart() { + stop + start +} + +reload() { + echo -n $"Reloading $SERVICE configuration: " + + killproc -p $PIDFILE $PROCESS -HUP + RETVAL=$? + echo + return $RETVAL +} + +# See how we were called. +case "$1" in + start|stop|restart|reload) + $1 + ;; + status) + status -p $PIDFILE $PROCESS + RETVAL=$? + ;; + force-reload) + reload + ;; + condrestart|try-restart) + [ -f ::localstatedir::/lock/subsys/$SERVICE ] && restart || : + ;; + *) + echo $"Usage: $0 {start|stop|status|restart|condrestart|reload|force-reload|try-restart}" + exit 2 + ;; +esac +exit $RETVAL diff --git a/src/locking/virtlockd.sysconf b/src/locking/virtlockd.sysconf new file mode 100644 index 0000000..d44dc46 --- /dev/null +++ b/src/locking/virtlockd.sysconf @@ -0,0 +1,3 @@ +# +# Pass extra arguments to virtlockd +#VIRTLOCKD_ARGS= -- 1.7.11.2

On 08/09/2012 09:20 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
The virtlockd daemon will maintain locks on behalf of libvirtd. There are two reasons for it to be separate
- Avoid risk of other libvirtd threads accidentally releasing fcntl() locks by opening + closing a file that is locked - Ensure locks can be preserved across libvirtd restarts. virtlockd will need to be able to re-exec itself while maintaining locks. This is simpler to achieve if its sole job is maintaining locks
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- .gitignore | 2 + cfg.mk | 6 +- libvirt.spec.in | 7 + po/POTFILES.in | 1 + src/Makefile.am | 86 ++++- src/locking/lock_daemon.c | 750 ++++++++++++++++++++++++++++++++++++++++++ src/locking/lock_daemon.h | 43 +++ src/locking/virtlockd.init.in | 93 ++++++ src/locking/virtlockd.sysconf | 3 + 9 files changed, 987 insertions(+), 4 deletions(-) create mode 100644 src/locking/lock_daemon.c create mode 100644 src/locking/lock_daemon.h create mode 100644 src/locking/virtlockd.init.in create mode 100644 src/locking/virtlockd.sysconf
I had a merge failure trying to apply this; you'll need a minor context rebase in cfg.mk to use this.
+++ b/cfg.mk @@ -636,6 +636,8 @@ sc_prohibit_cross_inclusion: @for dir in $(cross_dirs); do \ case $$dir in \ util/) safe="util";; \ + locking/) \ + safe="($$dir|util|conf|rpc)";; \
You added a new branch for locking...
cpu/ | locking/ | network/ | rpc/ | security/) \
...so this branch won't be reached, so you should clean it up while here.
+++ b/src/Makefile.am
+ +install-sysconfig: + mkdir -p $(DESTDIR)$(sysconfdir)/sysconfig
Shouldn't we be using $(MKDIR_P) instead?
+ $(INSTALL_DATA) $(srcdir)/locking/virtlockd.sysconf \ + $(DESTDIR)$(sysconfdir)/sysconfig/virtlockd + +uninstall-sysconfig: + rm -f $(DESTDIR)$(sysconfdir)/sysconfig/virtlockd + +EXTRA_DIST += locking/virtlockd.init.in + +if WITH_LIBVIRTD +if LIBVIRT_INIT_SCRIPT_RED_HAT +install-init:: virtlockd.init install-sysconfig + mkdir -p $(DESTDIR)$(sysconfdir)/rc.d/init.d
and again.
+++ b/src/locking/lock_daemon.c
+enum { + VIR_LOCK_DAEMON_ERR_NONE = 0, + VIR_LOCK_DAEMON_ERR_PIDFILE, + VIR_LOCK_DAEMON_ERR_RUNDIR, + VIR_LOCK_DAEMON_ERR_INIT, + VIR_LOCK_DAEMON_ERR_SIGNAL, + VIR_LOCK_DAEMON_ERR_PRIVS, + VIR_LOCK_DAEMON_ERR_NETWORK, + VIR_LOCK_DAEMON_ERR_CONFIG, + VIR_LOCK_DAEMON_ERR_HOOKS,
Is it worth using '= 1', '= 2', and so forth, to make future extensions aware that they must not be in the middle, since this is tied to RPC protocol?
+static int +virLockDaemonForkIntoBackground(const char *argv0) +{ + int statuspipe[2]; + if (pipe(statuspipe) < 0) + return -1; + + pid_t pid = fork(); + switch (pid) { + case 0: + { + int stdinfd = -1; + int stdoutfd = -1; + int nextpid; + + VIR_FORCE_CLOSE(statuspipe[0]); + + if ((stdinfd = open("/dev/null", O_RDONLY)) < 0) + goto cleanup; + if ((stdoutfd = open("/dev/null", O_WRONLY)) < 0) + goto cleanup; + if (dup2(stdinfd, STDIN_FILENO) != STDIN_FILENO) + goto cleanup; + if (dup2(stdoutfd, STDOUT_FILENO) != STDOUT_FILENO) + goto cleanup; + if (dup2(stdoutfd, STDERR_FILENO) != STDERR_FILENO) + goto cleanup; + if (VIR_CLOSE(stdinfd) < 0) + goto cleanup; + if (VIR_CLOSE(stdoutfd) < 0) + goto cleanup;
Oops - this can blindly re-close stdin or stdout or stderr if the parent process had those fds closed. It must be: if (stdinfd > STDERR_FILENO && VIR_CLOSE(stdinfd) < 0) goto cleanup; stdinfd = -1; if (stdoutfd > STDERR_FILENO && VIR_CLOSE(stdoutfd) < 0) goto cleanup; stdoutfd = -1;
+ + if (setsid() < 0) + goto cleanup; + + nextpid = fork(); + switch (nextpid) { + case 0: + return statuspipe[1]; + case -1: + return -1; + default: + _exit(0);
_exit(EXIT_SUCCESS) (I thought we had a syntax check for this - oh, I see - that rule checked for 'exit' but not '_exit' or '_Exit'. Gnulib patch coming up).
+ if (ret == 1 && status != 0) { + fprintf(stderr, + _("%s: error: %s. Check /var/log/messages or run without " + "--daemon for more info.\n"), argv0, + virDaemonErrTypeToString(status)); + } + _exit(ret == 1 && status == 0 ? 0 : 1);
Another case where EXIT_{SUCCESS,FAILURE} looks better.
+ + /* + * Command line override for --verbose + */ + if ((verbose) && (virLogGetDefaultPriority() > VIR_LOG_INFO))
Copy and paste, but these are redundant (). Why are we copying so much code? Should some of this live in a common file under util, to be used by both this file and daemon/libvirtd.c?
+static int +virLockDaemonSetupSignals(virNetServerPtr srv) +{ + if (virNetServerAddSignalHandler(srv, SIGINT, virLockDaemonShutdownHandler, NULL) < 0) + return -1; + if (virNetServerAddSignalHandler(srv, SIGQUIT, virLockDaemonShutdownHandler, NULL) < 0) + return -1; + if (virNetServerAddSignalHandler(srv, SIGTERM, virLockDaemonShutdownHandler, NULL) < 0) + return -1;
Do we need to worry about setting SIGPIPE to a known state?
+static void +virLockDaemonUsage(const char *argv0) +{ + fprintf (stderr, + _("\n\ +Usage:\n\ + %s [options]\n\ +\n\ +Options:\n\ + -v | --verbose Verbose messages.\n\ + -d | --daemon Run as a daemon & write PID file.\n\ + -t | --timeout <secs> Exit after timeout period.\n\ + -f | --config <file> Configuration file.\n\ + | --version Display version information.\n\ + -p | --pid-file <file> Change name of PID file.\n\
Shouldn't we also list '--help' as an option?
+ +enum { + OPT_VERSION = 129
129 is still a valid character in single-byte locales, which someone could pass in from the command line via a short option and possibly mess up getopt_long. I think you want it to be 256 or greater.
+ struct option opts[] = { + { "verbose", no_argument, &verbose, 1}, + { "daemon", no_argument, &godaemon, 1},
Why do these two use the non-NULL flag argument, instead of the more typical "NULL, 'd'" layout...
+ { "config", required_argument, NULL, 'f'}, + { "timeout", required_argument, NULL, 't'}, + { "pid-file", required_argument, NULL, 'p'}, + { "version", no_argument, NULL, OPT_VERSION }, + { "help", no_argument, NULL, '?' }, + {0, 0, 0, 0} + }; +
+ switch (c) { + case 0: + /* Got one of the flags */ + break; + case 'v': + verbose = 1; + break; + case 'd': + godaemon = 1; + break;
...especially since here you also handle short options for the same task, which do the same work? (I know, copy and paste from a bad example in libvirtd.c)
+ + case '?': + virLockDaemonUsage(argv[0]); + return 2;
That says that 'virtlockd --help' will fail with status 2, even when it successfully printed the usage message. I think you need to route --help slightly differently than the default handling of getopt_long error handling, if only to control the exit status differently (again, copying from a bad example). Looks mostly reasonable. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

From: "Daniel P. Berrange" <berrange@redhat.com> The virtlockd daemon will be responsible for managing locks on virtual machines. Communication will be via the standard RPC infrastructure. This provides the XDR protocol definition * src/locking/lock_protocol.x: Wire protocol for virtlockd * src/Makefile.am: Include lock_protocol.[ch] in virtlockd Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- .gitignore | 1 + cfg.mk | 3 ++ src/Makefile.am | 14 ++++++- src/locking/lock_protocol.x | 89 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 src/locking/lock_protocol.x diff --git a/.gitignore b/.gitignore index 737a7fb..e7d3495 100644 --- a/.gitignore +++ b/.gitignore @@ -102,6 +102,7 @@ /src/libvirt_*helper /src/libvirt_*probes.h /src/libvirt_lxc +/src/locking/lock_protocol.[ch] /src/locking/qemu-sanlock.conf /src/locking/test_libvirt_sanlock.aug /src/lxc/lxc_controller_dispatch.h diff --git a/cfg.mk b/cfg.mk index bf3d04d..f0b9e45 100644 --- a/cfg.mk +++ b/cfg.mk @@ -813,3 +813,6 @@ exclude_file_name_regexp--sc_unmarked_diagnostics = \ ^(docs/apibuild.py|tests/virt-aa-helper-test)$$ exclude_file_name_regexp--sc_size_of_brackets = cfg.mk + +exclude_file_name_regexp--sc_correct_id_types = \ + (^src/locking/lock_protocol.x$$) diff --git a/src/Makefile.am b/src/Makefile.am index 3e960a8..ceb953a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -140,6 +140,15 @@ DRIVER_SOURCES = \ LOCK_DRIVER_SANLOCK_SOURCES = \ locking/lock_driver_sanlock.c +LOCK_PROTOCOL_GENERATED = \ + locking/lock_protocol.h \ + locking/lock_protocol.c \ + $(NULL) + +EXTRA_DIST += locking/lock_protocol.x +BUILT_SOURCES += $(LOCK_PROTOCOL_GENERATED) +MAINTAINERCLEANFILES += $(LOCK_PROTOCOL_GENERATED) + LOCK_DAEMON_SOURCES = \ locking/lock_daemon.h \ locking/lock_daemon.c \ @@ -1459,7 +1468,10 @@ EXTRA_DIST += $(LIBVIRT_QEMU_SYMBOL_FILE) if WITH_LIBVIRTD sbin_PROGRAMS = virtlockd -virtlockd_SOURCES = $(LOCK_DAEMON_SOURCES) +virtlockd_SOURCES = \ + $(LOCK_DAEMON_SOURCES) \ + $(LOCK_PROTOCOL_GENERATED) \ + $(NULL) virtlockd_CFLAGS = \ $(AM_CFLAGS) \ $(NULL) diff --git a/src/locking/lock_protocol.x b/src/locking/lock_protocol.x new file mode 100644 index 0000000..d3f6fb1 --- /dev/null +++ b/src/locking/lock_protocol.x @@ -0,0 +1,89 @@ +/* -*- c -*- + */ + +%#include "internal.h" + +typedef opaque virLockSpaceProtocolUUID[VIR_UUID_BUFLEN]; + +/* Length of long, but not unbounded, strings. + * This is an arbitrary limit designed to stop the decoder from trying + * to allocate unbounded amounts of memory when fed with a bad message. + */ +const VIR_LOCK_SPACE_PROTOCOL_STRING_MAX = 65536; + +/* A long string, which may NOT be NULL. */ +typedef string virLockSpaceProtocolNonNullString<VIR_LOCK_SPACE_PROTOCOL_STRING_MAX>; + +/* A long string, which may be NULL. */ +typedef virLockSpaceProtocolNonNullString *virLockSpaceProtocolString; + +struct virLockSpaceProtocolOwner { + virLockSpaceProtocolUUID uuid; + virLockSpaceProtocolNonNullString name; + unsigned int id; + unsigned int pid; +}; + +struct virLockSpaceProtocolRegisterArgs { + virLockSpaceProtocolOwner owner; + unsigned int flags; +}; + +struct virLockSpaceProtocolRestrictArgs { + unsigned int flags; +}; + +struct virLockSpaceProtocolNewArgs { + virLockSpaceProtocolNonNullString path; + unsigned int flags; +}; + +struct virLockSpaceProtocolCreateResourceArgs { + virLockSpaceProtocolNonNullString path; + virLockSpaceProtocolNonNullString name; + unsigned int flags; +}; + +struct virLockSpaceProtocolDeleteResourceArgs { + virLockSpaceProtocolNonNullString path; + virLockSpaceProtocolNonNullString name; + unsigned int flags; +}; + +enum virLockSpaceProtocolAcquireResourceFlags { + VIR_LOCK_SPACE_PROTOCOL_ACQUIRE_RESOURCE_SHARED = 1, + VIR_LOCK_SPACE_PROTOCOL_ACQUIRE_RESOURCE_AUTOCREATE = 2 +}; + +struct virLockSpaceProtocolAcquireResourceArgs { + virLockSpaceProtocolNonNullString path; + virLockSpaceProtocolNonNullString name; + unsigned int flags; +}; + +struct virLockSpaceProtocolReleaseResourceArgs { + virLockSpaceProtocolNonNullString path; + virLockSpaceProtocolNonNullString name; + unsigned int flags; +}; + + +/* Define the program number, protocol version and procedure numbers here. */ +const VIR_LOCK_SPACE_PROTOCOL_PROGRAM = 0xEA7BEEF; +const VIR_LOCK_SPACE_PROTOCOL_PROGRAM_VERSION = 1; + +enum virLockSpaceProtocolProcedure { + /* Each function must have a two-word comment. The first word is + * whether remote_generator.pl handles daemon, the second whether + * it handles src/remote. Additional flags can be specified after a + * pipe. + */ + VIR_LOCK_SPACE_PROTOCOL_PROC_REGISTER = 1, /* skipgen skipgen */ + VIR_LOCK_SPACE_PROTOCOL_PROC_RESTRICT = 2, /* skipgen skipgen */ + VIR_LOCK_SPACE_PROTOCOL_PROC_NEW = 3, /* skipgen skipgen */ + VIR_LOCK_SPACE_PROTOCOL_PROC_CREATE_RESOURCE = 4, /* skipgen skipgen */ + VIR_LOCK_SPACE_PROTOCOL_PROC_DELETE_RESOURCE = 5, /* skipgen skipgen */ + + VIR_LOCK_SPACE_PROTOCOL_PROC_ACQUIRE_RESOURCE = 6, /* skipgen skipgen */ + VIR_LOCK_SPACE_PROTOCOL_PROC_RELEASE_RESOURCE = 7 /* skipgen skipgen */ +}; -- 1.7.11.2

On 08/09/2012 09:20 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
The virtlockd daemon will be responsible for managing locks on virtual machines. Communication will be via the standard RPC infrastructure. This provides the XDR protocol definition
* src/locking/lock_protocol.x: Wire protocol for virtlockd * src/Makefile.am: Include lock_protocol.[ch] in virtlockd
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- .gitignore | 1 + cfg.mk | 3 ++ src/Makefile.am | 14 ++++++- src/locking/lock_protocol.x | 89 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 src/locking/lock_protocol.x
Incomplete: squash this in so that we also use 'pdwtags' to ensure on-the-wire compatibility: diff --git c/src/Makefile.am i/src/Makefile.am index 30a5b42..ab228a5 100644 --- c/src/Makefile.am +++ i/src/Makefile.am @@ -278,7 +278,7 @@ EXTRA_DIST += $(REMOTE_DRIVER_PROTOCOL) \ # The alternation of the following regexps matches both cases. r1 = /\* \d+ \*/ r2 = /\* <[[:xdigit:]]+> \S+:\d+ \*/ -struct_prefix = (remote_|qemu_|virNet|keepalive_) +struct_prefix = (remote_|qemu_|virNet|keepalive_|virLockSpace) PDWTAGS = \ $(AM_V_GEN)if (pdwtags --help) > /dev/null 2>&1; then \ @@ -326,7 +326,9 @@ PROTOCOL_STRUCTS = \ $(srcdir)/remote_protocol-structs \ $(srcdir)/qemu_protocol-structs \ $(srcdir)/virnetprotocol-structs \ - $(srcdir)/virkeepaliveprotocol-structs + $(srcdir)/virkeepaliveprotocol-structs \ + $(srcdir)/lock_protocol-structs \ + $(NULL) if WITH_REMOTE check-protocol: $(PROTOCOL_STRUCTS) $(PROTOCOL_STRUCTS:structs=struct) @@ -338,6 +340,9 @@ $(srcdir)/remote_protocol-struct $(srcdir)/qemu_protocol-struct: \ $(srcdir)/virnetprotocol-struct $(srcdir)/virkeepaliveprotocol-struct: \ $(srcdir)/%-struct: libvirt_net_rpc_la-%.lo $(PDWTAGS) +# lock_protocol is built without libtool +$(srcdir)/lock_protocol-struct: $(srcdir)/%-struct: virtlockd-%.o + $(PDWTAGS) else !WITH_REMOTE # The $(PROTOCOL_STRUCTS) files must live in git, because they cannot be # re-generated when configured --without-remote. diff --git c/src/lock_protocol-structs i/src/lock_protocol-structs new file mode 100644 index 0000000..444bdb1 --- /dev/null +++ i/src/lock_protocol-structs @@ -0,0 +1,51 @@ +/* -*- c -*- */ +struct virLockSpaceProtocolOwner { + virLockSpaceProtocolUUID uuid; + virLockSpaceProtocolNonNullString name; + u_int id; + u_int pid; +}; +struct virLockSpaceProtocolRegisterArgs { + virLockSpaceProtocolOwner owner; + u_int flags; +}; +struct virLockSpaceProtocolRestrictArgs { + u_int flags; +}; +struct virLockSpaceProtocolNewArgs { + virLockSpaceProtocolNonNullString path; + u_int flags; +}; +struct virLockSpaceProtocolCreateResourceArgs { + virLockSpaceProtocolNonNullString path; + virLockSpaceProtocolNonNullString name; + u_int flags; +}; +struct virLockSpaceProtocolDeleteResourceArgs { + virLockSpaceProtocolNonNullString path; + virLockSpaceProtocolNonNullString name; + u_int flags; +}; +enum virLockSpaceProtocolAcquireResourceFlags { + VIR_LOCK_SPACE_PROTOCOL_ACQUIRE_RESOURCE_SHARED = 1, + VIR_LOCK_SPACE_PROTOCOL_ACQUIRE_RESOURCE_AUTOCREATE = 2, +}; +struct virLockSpaceProtocolAcquireResourceArgs { + virLockSpaceProtocolNonNullString path; + virLockSpaceProtocolNonNullString name; + u_int flags; +}; +struct virLockSpaceProtocolReleaseResourceArgs { + virLockSpaceProtocolNonNullString path; + virLockSpaceProtocolNonNullString name; + u_int flags; +}; +enum virLockSpaceProtocolProcedure { + VIR_LOCK_SPACE_PROTOCOL_PROC_REGISTER = 1, + VIR_LOCK_SPACE_PROTOCOL_PROC_RESTRICT = 2, + VIR_LOCK_SPACE_PROTOCOL_PROC_NEW = 3, + VIR_LOCK_SPACE_PROTOCOL_PROC_CREATE_RESOURCE = 4, + VIR_LOCK_SPACE_PROTOCOL_PROC_DELETE_RESOURCE = 5, + VIR_LOCK_SPACE_PROTOCOL_PROC_ACQUIRE_RESOURCE = 6, + VIR_LOCK_SPACE_PROTOCOL_PROC_RELEASE_RESOURCE = 7, +}; [oh yuck - on re-reading that, we may have issues if someone configures where WITH_REMOTE and WITH_LIBVIRTD are not both empty or both set - I may have to do followup patches for those scenarios].
+++ b/cfg.mk @@ -813,3 +813,6 @@ exclude_file_name_regexp--sc_unmarked_diagnostics = \ ^(docs/apibuild.py|tests/virt-aa-helper-test)$$
exclude_file_name_regexp--sc_size_of_brackets = cfg.mk + +exclude_file_name_regexp--sc_correct_id_types = \ + (^src/locking/lock_protocol.x$$)
Not sorted.
+++ b/src/locking/lock_protocol.x +struct virLockSpaceProtocolOwner { + virLockSpaceProtocolUUID uuid; + virLockSpaceProtocolNonNullString name; + unsigned int id; + unsigned int pid;
Are we sure that this will always be wide enough? That is, does the virtlockd binary make sense on mingw, where pid_t is 64 bits, or are we reasonably sure that we are safe with restricting the RPC call to 32 bits because it will only ever be used on UNIX-y systems?
+enum virLockSpaceProtocolProcedure { + /* Each function must have a two-word comment. The first word is + * whether remote_generator.pl handles daemon, the second whether + * it handles src/remote. Additional flags can be specified after a + * pipe. + */ + VIR_LOCK_SPACE_PROTOCOL_PROC_REGISTER = 1, /* skipgen skipgen */ + VIR_LOCK_SPACE_PROTOCOL_PROC_RESTRICT = 2, /* skipgen skipgen */ + VIR_LOCK_SPACE_PROTOCOL_PROC_NEW = 3, /* skipgen skipgen */ + VIR_LOCK_SPACE_PROTOCOL_PROC_CREATE_RESOURCE = 4, /* skipgen skipgen */ + VIR_LOCK_SPACE_PROTOCOL_PROC_DELETE_RESOURCE = 5, /* skipgen skipgen */ + + VIR_LOCK_SPACE_PROTOCOL_PROC_ACQUIRE_RESOURCE = 6, /* skipgen skipgen */ + VIR_LOCK_SPACE_PROTOCOL_PROC_RELEASE_RESOURCE = 7 /* skipgen skipgen */
I guess you didn't bother hooking this up to remote_generator.pl :) For only 7 functions, that's probably okay. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

From: "Daniel P. Berrange" <berrange@redhat.com> Introduce a lock_daemon_dispatch.c file which implements the server side dispatcher the RPC APIs previously defined in the lock protocol. Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- .gitignore | 1 + po/POTFILES.in | 1 + src/Makefile.am | 14 ++ src/internal.h | 22 +++ src/locking/lock_daemon.c | 130 ++++++++++++- src/locking/lock_daemon.h | 13 ++ src/locking/lock_daemon_dispatch.c | 370 +++++++++++++++++++++++++++++++++++++ src/locking/lock_daemon_dispatch.h | 31 ++++ 8 files changed, 580 insertions(+), 2 deletions(-) create mode 100644 src/locking/lock_daemon_dispatch.c create mode 100644 src/locking/lock_daemon_dispatch.h diff --git a/.gitignore b/.gitignore index e7d3495..c89d2a7 100644 --- a/.gitignore +++ b/.gitignore @@ -103,6 +103,7 @@ /src/libvirt_*probes.h /src/libvirt_lxc /src/locking/lock_protocol.[ch] +/src/locking/lock_daemon_dispatch_stubs.h /src/locking/qemu-sanlock.conf /src/locking/test_libvirt_sanlock.aug /src/lxc/lxc_controller_dispatch.h diff --git a/po/POTFILES.in b/po/POTFILES.in index 992c77d..6fe9690 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -41,6 +41,7 @@ src/internal.h src/libvirt.c src/libvirt-qemu.c src/locking/lock_daemon.c +src/locking/lock_daemon_dispatch.c src/locking/lock_driver_sanlock.c src/locking/lock_manager.c src/lxc/lxc_cgroup.c diff --git a/src/Makefile.am b/src/Makefile.am index ceb953a..6005ed5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -149,11 +149,24 @@ EXTRA_DIST += locking/lock_protocol.x BUILT_SOURCES += $(LOCK_PROTOCOL_GENERATED) MAINTAINERCLEANFILES += $(LOCK_PROTOCOL_GENERATED) +LOCK_DAEMON_GENERATED = \ + locking/lock_daemon_dispatch_stubs.h + $(NULL) + +BUILT_SOURCES += $(LOCK_DAEMON_GENERATED) +MAINTAINERCLEANFILES += $(LOCK_DAEMON_GENERATED) + LOCK_DAEMON_SOURCES = \ locking/lock_daemon.h \ locking/lock_daemon.c \ + locking/lock_daemon_dispatch.c \ + locking/lock_daemon_dispatch.h \ $(NULL) +$(srcdir)/locking/lock_daemon_dispatch_stubs.h: locking/lock_protocol.x $(srcdir)/rpc/gendispatch.pl Makefile.am + $(AM_V_GEN)perl -w $(srcdir)/rpc/gendispatch.pl -b virLockSpaceProtocol VIR_LOCK_SPACE_PROTOCOL $< > $@ + + NETDEV_CONF_SOURCES = \ conf/netdev_bandwidth_conf.h conf/netdev_bandwidth_conf.c \ conf/netdev_vport_profile_conf.h conf/netdev_vport_profile_conf.c @@ -1471,6 +1484,7 @@ sbin_PROGRAMS = virtlockd virtlockd_SOURCES = \ $(LOCK_DAEMON_SOURCES) \ $(LOCK_PROTOCOL_GENERATED) \ + $(LOCK_DAEMON_GENERATED) \ $(NULL) virtlockd_CFLAGS = \ $(AM_CFLAGS) \ diff --git a/src/internal.h b/src/internal.h index fd8d190..bd3b663 100644 --- a/src/internal.h +++ b/src/internal.h @@ -240,6 +240,28 @@ } \ } while (0) +/** + * virCheckFlagsGoto: + * @supported: an OR'ed set of supported flags + * @label: label to jump to on error + * + * To avoid memory leaks this macro has to be used before any non-trivial + * code which could possibly allocate some memory. + * + * Returns nothing. Jumps to a label if unsupported flags were + * passed to it. + */ +# define virCheckFlagsGoto(supported, label) \ + do { \ + unsigned long __unsuppflags = flags & ~(supported); \ + if (__unsuppflags) { \ + virReportInvalidArg(flags, \ + _("unsupported flags (0x%lx) in function %s"), \ + __unsuppflags, __FUNCTION__); \ + goto label; \ + } \ + } while (0) + # define virCheckNonNullArgReturn(argname, retval) \ do { \ if (argname == NULL) { \ diff --git a/src/locking/lock_daemon.c b/src/locking/lock_daemon.c index 287ad8c..94b41db 100644 --- a/src/locking/lock_daemon.c +++ b/src/locking/lock_daemon.c @@ -43,6 +43,9 @@ #include "virrandom.h" #include "virhash.h" +#include "locking/lock_daemon_dispatch.h" +#include "locking/lock_protocol.h" + #include "configmake.h" #define VIR_FROM_THIS VIR_FROM_LOCKING @@ -54,6 +57,8 @@ struct _virLockDaemon { virMutex lock; virNetServerPtr srv; + virHashTablePtr lockspaces; + virLockSpacePtr defaultLockspace; }; virLockDaemonPtr lockDaemon = NULL; @@ -99,11 +104,19 @@ virLockDaemonFree(virLockDaemonPtr lockd) return; virObjectUnref(lockd->srv); + virHashFree(lockd->lockspaces); + virLockSpaceFree(lockd->defaultLockspace); VIR_FREE(lockd); } +static void virLockDaemonLockSpaceDataFree(void *data, + const void *key ATTRIBUTE_UNUSED) +{ + virLockSpaceFree(data); +} + static virLockDaemonPtr virLockDaemonNew(void) { @@ -130,6 +143,13 @@ virLockDaemonNew(void) NULL))) goto error; + if (!(lockd->lockspaces = virHashCreate(3, + virLockDaemonLockSpaceDataFree))) + goto error; + + if (!(lockd->defaultLockspace = virLockSpaceNew(NULL))) + goto error; + return lockd; error: @@ -138,6 +158,31 @@ error: } +int virLockDaemonAddLockSpace(virLockDaemonPtr lockd, + const char *path, + virLockSpacePtr lockspace) +{ + int ret; + virMutexLock(&lockd->lock); + ret = virHashAddEntry(lockd->lockspaces, path, lockspace); + virMutexUnlock(&lockd->lock); + return ret; +} + +virLockSpacePtr virLockDaemonFindLockSpace(virLockDaemonPtr lockd, + const char *path) +{ + virLockSpacePtr lockspace; + virMutexLock(&lockd->lock); + if (path && STRNEQ(path, "")) + lockspace = virHashLookup(lockd->lockspaces, path); + else + lockspace = lockd->defaultLockspace; + virMutexUnlock(&lockd->lock); + return lockspace; +} + + static int virLockDaemonForkIntoBackground(const char *argv0) { @@ -427,6 +472,30 @@ virLockDaemonSetupNetworking(virNetServerPtr srv, const char *sock_path) } +struct virLockDaemonClientReleaseData { + virLockDaemonClientPtr client; + bool hadSomeLeases; + bool gotError; +}; + +static void +virLockDaemonClientReleaseLockspace(void *payload, + const void *name ATTRIBUTE_UNUSED, + void *opaque) +{ + virLockSpacePtr lockspace = payload; + struct virLockDaemonClientReleaseData *data = opaque; + int rc; + + rc = virLockSpaceReleaseResourcesForOwner(lockspace, + data->client->clientPid); + if (rc > 0) + data->hadSomeLeases = true; + else if (rc < 0) + data->gotError = true; +} + + static void virLockDaemonClientFree(void *opaque) { @@ -435,9 +504,52 @@ virLockDaemonClientFree(void *opaque) if (!priv) return; - VIR_DEBUG("priv=%p client=%lld", + VIR_DEBUG("priv=%p client=%lld owner=%lld", priv, - (unsigned long long)priv->clientPid); + (unsigned long long)priv->clientPid, + (unsigned long long)priv->ownerPid); + + /* If client & owner match, this is the lock holder */ + if (priv->clientPid == priv->ownerPid) { + size_t i; + struct virLockDaemonClientReleaseData data = { + priv, false, false + }; + + /* Release all locks associated with this + * owner in all lockspaces */ + virMutexLock(&lockDaemon->lock); + virHashForEach(lockDaemon->lockspaces, + virLockDaemonClientReleaseLockspace, + &data); + virLockDaemonClientReleaseLockspace(lockDaemon->defaultLockspace, + "", + &data); + virMutexUnlock(&lockDaemon->lock); + + /* If the client had some active leases when it + * closed the connection, we must kill it off + * to make sure it doesn't do nasty stuff */ + if (data.gotError || data.hadSomeLeases) { + for (i = 0 ; i < 15 ; i++) { + int signum; + if (i == 0) + signum = SIGTERM; + else if (i == 8) + signum = SIGKILL; + else + signum = 0; + if (virKillProcess(priv->clientPid, signum) < 0) { + if (errno == ESRCH) + break; + + VIR_WARN("Failed to kill off pid %lld", + (unsigned long long)priv->clientPid); + } + usleep(200 * 1000); + } + } + } virMutexDestroy(&priv->lock); VIR_FREE(priv); @@ -548,6 +660,7 @@ enum { #define MAX_LISTEN 5 int main(int argc, char **argv) { + virNetServerProgramPtr lockProgram = NULL; const char *remote_config_file = NULL; int statuswrite = -1; int ret = 1; @@ -707,6 +820,18 @@ int main(int argc, char **argv) { goto cleanup; } + if (!(lockProgram = virNetServerProgramNew(VIR_LOCK_SPACE_PROTOCOL_PROGRAM, + VIR_LOCK_SPACE_PROTOCOL_PROGRAM_VERSION, + virLockSpaceProtocolProcs, + virLockSpaceProtocolNProcs))) { + ret = VIR_LOCK_DAEMON_ERR_INIT; + goto cleanup; + } + if (virNetServerAddProgram(lockDaemon->srv, lockProgram) < 0) { + ret = VIR_LOCK_DAEMON_ERR_INIT; + goto cleanup; + } + /* Disable error func, now logging is setup */ virSetErrorFunc(NULL, virLockDaemonErrorHandler); @@ -730,6 +855,7 @@ int main(int argc, char **argv) { ret = 0; cleanup: + virObjectUnref(lockProgram); virLockDaemonFree(lockDaemon); if (statuswrite != -1) { if (ret != 0) { diff --git a/src/locking/lock_daemon.h b/src/locking/lock_daemon.h index 7bc8c2e..619f8f2 100644 --- a/src/locking/lock_daemon.h +++ b/src/locking/lock_daemon.h @@ -34,10 +34,23 @@ typedef virLockDaemonClient *virLockDaemonClientPtr; struct _virLockDaemonClient { virMutex lock; + bool restricted; + + pid_t ownerPid; + char *ownerName; + unsigned char ownerUUID[VIR_UUID_BUFLEN]; + unsigned int ownerId; pid_t clientPid; }; extern virLockDaemonPtr lockDaemon; +int virLockDaemonAddLockSpace(virLockDaemonPtr lockd, + const char *path, + virLockSpacePtr lockspace); + +virLockSpacePtr virLockDaemonFindLockSpace(virLockDaemonPtr lockd, + const char *path); + #endif /* __VIR_LOCK_DAEMON_H__ */ diff --git a/src/locking/lock_daemon_dispatch.c b/src/locking/lock_daemon_dispatch.c new file mode 100644 index 0000000..664bad5 --- /dev/null +++ b/src/locking/lock_daemon_dispatch.c @@ -0,0 +1,370 @@ +/* + * lock_daemon_dispatch.c: lock management daemon dispatch + * + * Copyright (C) 2006-2012 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/>. + * + * Author: Daniel P. Berrange <berrange@redhat.com> + */ + +#include <config.h> + +#include "rpc/virnetserver.h" +#include "rpc/virnetserverclient.h" +#include "util.h" +#include "logging.h" + +#include "lock_daemon.h" +#include "lock_protocol.h" +#include "lock_daemon_dispatch_stubs.h" +#include "virterror_internal.h" + +#define VIR_FROM_THIS VIR_FROM_RPC + +static int +virLockSpaceProtocolDispatchAcquireResource(virNetServerPtr server ATTRIBUTE_UNUSED, + virNetServerClientPtr client, + virNetMessagePtr msg ATTRIBUTE_UNUSED, + virNetMessageErrorPtr rerr, + virLockSpaceProtocolAcquireResourceArgs *args) +{ + int rv = -1; + unsigned int flags = args->flags; + virLockDaemonClientPtr priv = + virNetServerClientGetPrivateData(client); + virLockSpacePtr lockspace; + unsigned int newFlags; + + virMutexLock(&priv->lock); + + virCheckFlagsGoto(VIR_LOCK_SPACE_ACQUIRE_SHARED | + VIR_LOCK_SPACE_ACQUIRE_AUTOCREATE, cleanup); + + if (priv->restricted) { + virReportError(VIR_ERR_OPERATION_DENIED, "%s", + _("lock manager connection has been restricted")); + goto cleanup; + } + + if (!priv->ownerPid) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("lock owner details have not been registered")); + goto cleanup; + } + + if (!(lockspace = virLockDaemonFindLockSpace(lockDaemon, args->path))) + goto cleanup; + + newFlags = 0; + if (flags & VIR_LOCK_SPACE_PROTOCOL_ACQUIRE_RESOURCE_SHARED) + newFlags |= VIR_LOCK_SPACE_ACQUIRE_SHARED; + if (flags & VIR_LOCK_SPACE_PROTOCOL_ACQUIRE_RESOURCE_AUTOCREATE) + newFlags |= VIR_LOCK_SPACE_ACQUIRE_AUTOCREATE; + + if (virLockSpaceAcquireResource(lockspace, + args->name, + priv->ownerPid, + newFlags) < 0) + goto cleanup; + + rv = 0; + +cleanup: + if (rv < 0) + virNetMessageSaveError(rerr); + virMutexUnlock(&priv->lock); + return rv; +} + + +static int +virLockSpaceProtocolDispatchCreateResource(virNetServerPtr server ATTRIBUTE_UNUSED, + virNetServerClientPtr client, + virNetMessagePtr msg ATTRIBUTE_UNUSED, + virNetMessageErrorPtr rerr, + virLockSpaceProtocolCreateResourceArgs *args) +{ + int rv = -1; + unsigned int flags = args->flags; + virLockDaemonClientPtr priv = + virNetServerClientGetPrivateData(client); + virLockSpacePtr lockspace; + + virMutexLock(&priv->lock); + + virCheckFlagsGoto(0, cleanup); + + if (priv->restricted) { + virReportError(VIR_ERR_OPERATION_DENIED, "%s", + _("lock manager connection has been restricted")); + goto cleanup; + } + + if (!priv->ownerPid) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("lock owner details have not been registered")); + goto cleanup; + } + + if (!(lockspace = virLockDaemonFindLockSpace(lockDaemon, args->path))) + goto cleanup; + + if (virLockSpaceCreateResource(lockspace, args->name) < 0) + goto cleanup; + + rv = 0; + +cleanup: + if (rv < 0) + virNetMessageSaveError(rerr); + virMutexUnlock(&priv->lock); + return rv; +} + + +static int +virLockSpaceProtocolDispatchDeleteResource(virNetServerPtr server ATTRIBUTE_UNUSED, + virNetServerClientPtr client, + virNetMessagePtr msg ATTRIBUTE_UNUSED, + virNetMessageErrorPtr rerr, + virLockSpaceProtocolDeleteResourceArgs *args) +{ + int rv = -1; + unsigned int flags = args->flags; + virLockDaemonClientPtr priv = + virNetServerClientGetPrivateData(client); + virLockSpacePtr lockspace; + + virMutexLock(&priv->lock); + + virCheckFlagsGoto(0, cleanup); + + if (priv->restricted) { + virReportError(VIR_ERR_OPERATION_DENIED, "%s", + _("lock manager connection has been restricted")); + goto cleanup; + } + + if (!priv->ownerPid) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("lock owner details have not been registered")); + goto cleanup; + } + + if (!(lockspace = virLockDaemonFindLockSpace(lockDaemon, args->path))) + goto cleanup; + + if (virLockSpaceDeleteResource(lockspace, args->name) < 0) + goto cleanup; + + rv = 0; + +cleanup: + if (rv < 0) + virNetMessageSaveError(rerr); + virMutexUnlock(&priv->lock); + return rv; +} + + +static int +virLockSpaceProtocolDispatchNew(virNetServerPtr server ATTRIBUTE_UNUSED, + virNetServerClientPtr client, + virNetMessagePtr msg ATTRIBUTE_UNUSED, + virNetMessageErrorPtr rerr, + virLockSpaceProtocolNewArgs *args) +{ + int rv = -1; + unsigned int flags = args->flags; + virLockDaemonClientPtr priv = + virNetServerClientGetPrivateData(client); + virLockSpacePtr lockspace; + + virMutexLock(&priv->lock); + + virCheckFlagsGoto(0, cleanup); + + if (priv->restricted) { + virReportError(VIR_ERR_OPERATION_DENIED, "%s", + _("lock manager connection has been restricted")); + goto cleanup; + } + + if (!priv->ownerPid) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("lock owner details have not been registered")); + goto cleanup; + } + + if (!args->path || STREQ(args->path, "")) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("the default lockspace already exists")); + goto cleanup; + } + + if ((lockspace = virLockDaemonFindLockSpace(lockDaemon, args->path))) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Lockspace for path %s already exists"), + args->path); + goto cleanup; + } + virResetLastError(); + + lockspace = virLockSpaceNew(args->path); + virLockDaemonAddLockSpace(lockDaemon, args->path, lockspace); + + rv = 0; + +cleanup: + if (rv < 0) + virNetMessageSaveError(rerr); + virMutexUnlock(&priv->lock); + return rv; +} + + +static int +virLockSpaceProtocolDispatchRegister(virNetServerPtr server ATTRIBUTE_UNUSED, + virNetServerClientPtr client, + virNetMessagePtr msg ATTRIBUTE_UNUSED, + virNetMessageErrorPtr rerr, + virLockSpaceProtocolRegisterArgs *args) +{ + int rv = -1; + unsigned int flags = args->flags; + virLockDaemonClientPtr priv = + virNetServerClientGetPrivateData(client); + + virMutexLock(&priv->lock); + + virCheckFlagsGoto(0, cleanup); + + if (priv->restricted) { + virReportError(VIR_ERR_OPERATION_DENIED, "%s", + _("lock manager connection has been restricted")); + goto cleanup; + } + + if (priv->ownerPid) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("lock owner details have already been registered")); + goto cleanup; + } + + if (!(priv->ownerName = strdup(args->owner.name))) { + virReportOOMError(); + goto cleanup; + } + memcpy(priv->ownerUUID, args->owner.uuid, VIR_UUID_BUFLEN); + priv->ownerId = args->owner.id; + priv->ownerPid = args->owner.pid; + VIR_DEBUG("ownerName=%s ownerId=%d ownerPid=%lld", + priv->ownerName, priv->ownerId, (unsigned long long)priv->ownerPid); + + rv = 0; + +cleanup: + if (rv < 0) + virNetMessageSaveError(rerr); + virMutexUnlock(&priv->lock); + return rv; +} + + +static int +virLockSpaceProtocolDispatchReleaseResource(virNetServerPtr server ATTRIBUTE_UNUSED, + virNetServerClientPtr client, + virNetMessagePtr msg ATTRIBUTE_UNUSED, + virNetMessageErrorPtr rerr, + virLockSpaceProtocolReleaseResourceArgs *args) +{ + int rv = -1; + unsigned int flags = args->flags; + virLockDaemonClientPtr priv = + virNetServerClientGetPrivateData(client); + virLockSpacePtr lockspace; + + virMutexLock(&priv->lock); + + virCheckFlagsGoto(0, cleanup); + + if (priv->restricted) { + virReportError(VIR_ERR_OPERATION_DENIED, "%s", + _("lock manager connection has been restricted")); + goto cleanup; + } + + if (!priv->ownerPid) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("lock owner details have not been registered")); + goto cleanup; + } + + if (!(lockspace = virLockDaemonFindLockSpace(lockDaemon, args->path))) + goto cleanup; + + if (virLockSpaceReleaseResource(lockspace, + args->name, + priv->ownerPid) < 0) + goto cleanup; + + rv = 0; + +cleanup: + if (rv < 0) + virNetMessageSaveError(rerr); + virMutexUnlock(&priv->lock); + return rv; +} + + +static int +virLockSpaceProtocolDispatchRestrict(virNetServerPtr server ATTRIBUTE_UNUSED, + virNetServerClientPtr client, + virNetMessagePtr msg ATTRIBUTE_UNUSED, + virNetMessageErrorPtr rerr, + virLockSpaceProtocolRestrictArgs *args) +{ + int rv = -1; + unsigned int flags = args->flags; + virLockDaemonClientPtr priv = + virNetServerClientGetPrivateData(client); + + virMutexLock(&priv->lock); + + virCheckFlagsGoto(0, cleanup); + + if (priv->restricted) { + virReportError(VIR_ERR_OPERATION_DENIED, "%s", + _("lock manager connection has been restricted")); + goto cleanup; + } + + if (!priv->ownerPid) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("lock owner details have not been registered")); + goto cleanup; + } + + priv->restricted = true; + rv = 0; + +cleanup: + if (rv < 0) + virNetMessageSaveError(rerr); + virMutexUnlock(&priv->lock); + return rv; +} diff --git a/src/locking/lock_daemon_dispatch.h b/src/locking/lock_daemon_dispatch.h new file mode 100644 index 0000000..a193a58 --- /dev/null +++ b/src/locking/lock_daemon_dispatch.h @@ -0,0 +1,31 @@ +/* + * lock_daemon_dispatch.h: lock management daemon dispatch + * + * Copyright (C) 2006-2012 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/>. + * + * Author: Daniel P. Berrange <berrange@redhat.com> + */ + +#ifndef __VIR_LOCK_DAEMON_DISPATCH_H__ +# define __VIR_LOCK_DAEMON_DISPATCH_H__ + +# include "rpc/virnetserverprogram.h" + +extern virNetServerProgramProc virLockSpaceProtocolProcs[]; +extern size_t virLockSpaceProtocolNProcs; + +#endif /* __VIR_LOCK_DAEMON_DISPATCH_H__ */ -- 1.7.11.2

On 08/09/2012 09:20 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
Introduce a lock_daemon_dispatch.c file which implements the server side dispatcher the RPC APIs previously defined in the lock protocol.
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- .gitignore | 1 + po/POTFILES.in | 1 + src/Makefile.am | 14 ++ src/internal.h | 22 +++ src/locking/lock_daemon.c | 130 ++++++++++++- src/locking/lock_daemon.h | 13 ++ src/locking/lock_daemon_dispatch.c | 370 +++++++++++++++++++++++++++++++++++++ src/locking/lock_daemon_dispatch.h | 31 ++++ 8 files changed, 580 insertions(+), 2 deletions(-) create mode 100644 src/locking/lock_daemon_dispatch.c create mode 100644 src/locking/lock_daemon_dispatch.h
I hit a merge conflict applying this one, as well (this time in src/Makefile.am); shouldn't be too hard to rebase.
+++ b/src/Makefile.am @@ -149,11 +149,24 @@ EXTRA_DIST += locking/lock_protocol.x BUILT_SOURCES += $(LOCK_PROTOCOL_GENERATED) MAINTAINERCLEANFILES += $(LOCK_PROTOCOL_GENERATED)
+LOCK_DAEMON_GENERATED = \ + locking/lock_daemon_dispatch_stubs.h + $(NULL)
Missing a \
+$(srcdir)/locking/lock_daemon_dispatch_stubs.h: locking/lock_protocol.x $(srcdir)/rpc/gendispatch.pl Makefile.am + $(AM_V_GEN)perl -w $(srcdir)/rpc/gendispatch.pl -b virLockSpaceProtocol VIR_LOCK_SPACE_PROTOCOL $< > $@
Long lines; some \ would be nice.
+++ b/src/internal.h @@ -240,6 +240,28 @@ } \ } while (0)
+/** + * virCheckFlagsGoto: + * @supported: an OR'ed set of supported flags + * @label: label to jump to on error + * + * To avoid memory leaks this macro has to be used before any non-trivial + * code which could possibly allocate some memory.
Stale comment - the goto is what lets you jump to an error label, and thus clean up any non-trivial code used before this macro.
+ /* If the client had some active leases when it + * closed the connection, we must kill it off + * to make sure it doesn't do nasty stuff */ + if (data.gotError || data.hadSomeLeases) { + for (i = 0 ; i < 15 ; i++) { + int signum; + if (i == 0) + signum = SIGTERM; + else if (i == 8) + signum = SIGKILL;
Rather than open-coding this loop, can't you use virPidAbort() from util/command.h?
+++ b/src/locking/lock_daemon_dispatch.c
+ +static int +virLockSpaceProtocolDispatchAcquireResource(virNetServerPtr server ATTRIBUTE_UNUSED, + virNetServerClientPtr client, + virNetMessagePtr msg ATTRIBUTE_UNUSED, + virNetMessageErrorPtr rerr, + virLockSpaceProtocolAcquireResourceArgs *args) +{ + int rv = -1; + unsigned int flags = args->flags;
If you check flags here...
+ virLockDaemonClientPtr priv = + virNetServerClientGetPrivateData(client);
...or delay the assignment of priv...
+ virLockSpacePtr lockspace; + unsigned int newFlags;
...and check flags here, with virCheckFlags(..., -1)...
+ + virMutexLock(&priv->lock); + + virCheckFlagsGoto(VIR_LOCK_SPACE_ACQUIRE_SHARED | + VIR_LOCK_SPACE_ACQUIRE_AUTOCREATE, cleanup);
...then you wouldn't need virCheckFlagsGoto. Then again, I don't mind having the new macro, as it gives you the option to put flag checks at a location easier to read.
+ + newFlags = 0; + if (flags & VIR_LOCK_SPACE_PROTOCOL_ACQUIRE_RESOURCE_SHARED) + newFlags |= VIR_LOCK_SPACE_ACQUIRE_SHARED; + if (flags & VIR_LOCK_SPACE_PROTOCOL_ACQUIRE_RESOURCE_AUTOCREATE) + newFlags |= VIR_LOCK_SPACE_ACQUIRE_AUTOCREATE;
Any reason you have two namespaces of flags, and have to translate between them, rather than guaranteeing that the flags have the same values and can be reused in both contexts? -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

From: "Daniel P. Berrange" <berrange@redhat.com> This enhancement virtlockd so that it can receive a pre-opened UNIX domain socket from systemd at launch time, and adds the systemd service/socket unit files * daemon/libvirtd.service.in: Require virtlockd to be running * libvirt.spec.in: Add virtlockd systemd files * src/Makefile.am: Install systemd files * src/locking/lock_daemon.c: Support socket activation * src/locking/virtlockd.service.in, src/locking/virtlockd.socket.in: systemd unit files * src/rpc/virnetserverservice.c, src/rpc/virnetserverservice.h: Add virNetServerServiceNewFD() method * src/rpc/virnetsocket.c, src/rpc/virnetsocket.h: Add virNetSocketNewListenFD method Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- libvirt.spec.in | 6 ++++ src/Makefile.am | 51 ++++++++++++++++++++++++++-- src/locking/lock_daemon.c | 73 ++++++++++++++++++++++++++++++++++++++-- src/locking/virtlockd.service.in | 13 +++++++ src/locking/virtlockd.socket.in | 8 +++++ 5 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 src/locking/virtlockd.service.in create mode 100644 src/locking/virtlockd.socket.in diff --git a/libvirt.spec.in b/libvirt.spec.in index 1cbeca2..5a5d724 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -1394,6 +1394,7 @@ done %if %{with_systemd} if [ $1 -eq 1 ] ; then # Initial installation + /bin/systemctl enable virtlockd.socket >/dev/null 2>&1 || : /bin/systemctl enable libvirtd.service >/dev/null 2>&1 || : /bin/systemctl enable cgconfig.service >/dev/null 2>&1 || : fi @@ -1418,8 +1419,10 @@ fi %if %{with_systemd} if [ $1 -eq 0 ] ; then # Package removal, not upgrade + /bin/systemctl --no-reload disable virtlockd.socket > /dev/null 2>&1 || : /bin/systemctl --no-reload disable libvirtd.service > /dev/null 2>&1 || : /bin/systemctl stop libvirtd.service > /dev/null 2>&1 || : + /bin/systemctl stop virtlockd.service > /dev/null 2>&1 || : fi %else if [ $1 = 0 ]; then @@ -1433,6 +1436,7 @@ fi /bin/systemctl daemon-reload >/dev/null 2>&1 || : if [ $1 -ge 1 ] ; then # Package upgrade, not uninstall + /bin/systemctl try-restart virtlockd.service >/dev/null 2>&1 || : /bin/systemctl try-restart libvirtd.service >/dev/null 2>&1 || : fi %endif @@ -1531,6 +1535,8 @@ fi %{_sysconfdir}/rc.d/init.d/virtlockd %if %{with_systemd} %{_unitdir}/libvirtd.service +%{_unitdir}/virtlockd.service +%{_unitdir}/virtlockd.socket %endif %doc daemon/libvirtd.upstart %config(noreplace) %{_sysconfdir}/sysconfig/libvirtd diff --git a/src/Makefile.am b/src/Makefile.am index 6005ed5..35e8338 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1552,6 +1552,53 @@ virtlockd.init: locking/virtlockd.init.in $(top_builddir)/config.status +EXTRA_DIST += locking/virtlockd.service.in locking/virtlockd.socket.in + +if WITH_LIBVIRTD +if LIBVIRT_INIT_SCRIPT_SYSTEMD + +SYSTEMD_UNIT_DIR = /lib/systemd/system + +BUILT_SOURCES += virtlockd.service virtlockd.socket + +install-systemd: virtlockd.init install-sysconfig + mkdir -p $(DESTDIR)$(SYSTEMD_UNIT_DIR) + $(INSTALL_SCRIPT) virtlockd.service \ + $(DESTDIR)$(SYSTEMD_UNIT_DIR)/ + $(INSTALL_SCRIPT) virtlockd.socket \ + $(DESTDIR)$(SYSTEMD_UNIT_DIR)/ + +uninstall-systemd: uninstall-sysconfig + rm -f $(DESTDIR)$(SYSTEMD_UNIT_DIR)/virtlockd.service + rm -f $(DESTDIR)$(SYSTEMD_UNIT_DIR)/virtlockd.socket +else +install-systemd: +uninstall-systemd: +endif +else +install-systemd: +uninstall-systemd: +endif + +virtlockd.service: locking/virtlockd.service.in $(top_builddir)/config.status + $(AM_V_GEN)sed \ + -e "s!::localstatedir::!$(localstatedir)!g" \ + -e "s!::sbindir::!$(sbindir)!g" \ + -e "s!::sysconfdir::!$(sysconfdir)!g" \ + < $< > $@-t && \ + chmod a+x $@-t && \ + mv $@-t $@ + +virtlockd.socket: locking/virtlockd.socket.in $(top_builddir)/config.status + $(AM_V_GEN)sed \ + -e "s!::localstatedir::!$(localstatedir)!g" \ + -e "s!::sbindir::!$(sbindir)!g" \ + -e "s!::sysconfdir::!$(sysconfdir)!g" \ + < $< > $@-t && \ + chmod a+x $@-t && \ + mv $@-t $@ + + if HAVE_SANLOCK lockdriverdir = $(libdir)/libvirt/lock-driver lockdriver_LTLIBRARIES = sanlock.la @@ -1759,7 +1806,7 @@ endif endif EXTRA_DIST += $(SECURITY_DRIVER_APPARMOR_HELPER_SOURCES) -install-data-local: install-init +install-data-local: install-init install-systemd if WITH_LIBVIRTD $(MKDIR_P) "$(DESTDIR)$(localstatedir)/lib/libvirt/lockd" $(MKDIR_P) "$(DESTDIR)$(localstatedir)/run/libvirt/lockd" @@ -1809,7 +1856,7 @@ if WITH_NETWORK $(DESTDIR)$(sysconfdir)/libvirt/qemu/networks/autostart/default.xml endif -uninstall-local:: uninstall-init +uninstall-local:: uninstall-init uninstall-systemd if WITH_LIBVIRTD rmdir "$(DESTDIR)$(localstatedir)/lib/libvirt/lockd" ||: rmdir "$(DESTDIR)$(localstatedir)/run/libvirt/lockd" ||: diff --git a/src/locking/lock_daemon.c b/src/locking/lock_daemon.c index 94b41db..7bc6917 100644 --- a/src/locking/lock_daemon.c +++ b/src/locking/lock_daemon.c @@ -454,8 +454,69 @@ virLockDaemonSetupSignals(virNetServerPtr srv) return 0; } + static int -virLockDaemonSetupNetworking(virNetServerPtr srv, const char *sock_path) +virLockDaemonSetupNetworkingSystemD(virNetServerPtr srv) +{ + virNetServerServicePtr svc; + const char *pidstr; + const char *fdstr; + unsigned long long procid; + unsigned int nfds; + + if (!(pidstr = getenv("LISTEN_PID"))) { + VIR_DEBUG("No LISTEN_FDS from systemd"); + return 0; + } + + if (virStrToLong_ull(pidstr, NULL, 10, &procid) < 0) { + VIR_DEBUG("Malformed LISTEN_PID from systemd %s", pidstr); + return 0; + } + + if ((pid_t)procid != getpid()) { + VIR_DEBUG("LISTEN_PID %s is not for us %llu", + pidstr, (unsigned long long)getpid()); + return 0; + } + + if (!(fdstr = getenv("LISTEN_FDS"))) { + VIR_DEBUG("No LISTEN_FDS from systemd"); + return 0; + } + + if (virStrToLong_ui(fdstr, NULL, 10, &nfds) < 0) { + VIR_DEBUG("Malformed LISTEN_FDS from systemd %s", fdstr); + return 0; + } + + if (nfds > 1) { + VIR_DEBUG("Too many (%d) file descriptors from systemd", + nfds); + nfds = 1; + } + + unsetenv("LISTEN_PID"); + unsetenv("LISTEN_FDS"); + + if (nfds == 0) + return 0; + + /* Systemd passes FDs, starting immediately after stderr, + * so the first FD we'll get is '3'. */ + if (!(svc = virNetServerServiceNewFD(3, 0, false, 1, NULL))) + return -1; + + if (virNetServerAddService(srv, svc, NULL) < 0) { + virObjectUnref(svc); + return -1; + } + return 1; +} + + +static int +virLockDaemonSetupNetworkingNative(virNetServerPtr srv, const char *sock_path) { virNetServerServicePtr svc; @@ -672,6 +733,7 @@ int main(int argc, char **argv) { char *pid_file = NULL; int pid_file_fd = -1; char *sock_file = NULL; + int rv; struct option opts[] = { { "verbose", no_argument, &verbose, 1}, @@ -810,7 +872,14 @@ int main(int argc, char **argv) { goto cleanup; } - if (virLockDaemonSetupNetworking(lockDaemon->srv, sock_file) < 0) { + if ((rv = virLockDaemonSetupNetworkingSystemD(lockDaemon->srv)) < 0) { + ret = VIR_LOCK_DAEMON_ERR_NETWORK; + goto cleanup; + } + + /* Only do this, if systemd did not pass a FD */ + if (rv == 0 && + virLockDaemonSetupNetworkingNative(lockDaemon->srv, sock_file) < 0) { ret = VIR_LOCK_DAEMON_ERR_NETWORK; goto cleanup; } diff --git a/src/locking/virtlockd.service.in b/src/locking/virtlockd.service.in new file mode 100644 index 0000000..a9f9f93 --- /dev/null +++ b/src/locking/virtlockd.service.in @@ -0,0 +1,13 @@ +[Unit] +Description=Virtual machine lock manager +Requires=virtlockd.socket +After=syslog.target + +[Service] +EnvironmentFile=-/etc/sysconfig/virtlockd +ExecStart=@sbindir@/virtlockd +ExecReload=/bin/kill -HUP $MAINPID +# Loosing the locks is a really bad thing that will +# cause the machine to be fenced (rebooted), so make +# sure we discourage OOM killer +OOMScoreAdjust=-900 diff --git a/src/locking/virtlockd.socket.in b/src/locking/virtlockd.socket.in new file mode 100644 index 0000000..0589a29 --- /dev/null +++ b/src/locking/virtlockd.socket.in @@ -0,0 +1,8 @@ +[Unit] +Description=Virtual machine lock manager socket + +[Socket] +ListenStream=/var/run/libvirt/virtlockd/virtlockd.sock + +[Install] +WantedBy=multi-user.target -- 1.7.11.2

On 08/09/2012 09:20 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
This enhancement virtlockd so that it can receive a pre-opened
s/enhancement/enhances/
UNIX domain socket from systemd at launch time, and adds the systemd service/socket unit files
* daemon/libvirtd.service.in: Require virtlockd to be running * libvirt.spec.in: Add virtlockd systemd files * src/Makefile.am: Install systemd files * src/locking/lock_daemon.c: Support socket activation * src/locking/virtlockd.service.in, src/locking/virtlockd.socket.in: systemd unit files * src/rpc/virnetserverservice.c, src/rpc/virnetserverservice.h: Add virNetServerServiceNewFD() method * src/rpc/virnetsocket.c, src/rpc/virnetsocket.h: Add virNetSocketNewListenFD method
I didn't closely read the systemd documentation to see if you integrated correctly, so I have to assume that you tested this and that it worked. That said, I'll review the C code for accuracy.
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- libvirt.spec.in | 6 ++++ src/Makefile.am | 51 ++++++++++++++++++++++++++-- src/locking/lock_daemon.c | 73 ++++++++++++++++++++++++++++++++++++++-- src/locking/virtlockd.service.in | 13 +++++++ src/locking/virtlockd.socket.in | 8 +++++ 5 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 src/locking/virtlockd.service.in create mode 100644 src/locking/virtlockd.socket.in
+++ b/src/Makefile.am @@ -1552,6 +1552,53 @@ virtlockd.init: locking/virtlockd.init.in $(top_builddir)/config.status
+EXTRA_DIST += locking/virtlockd.service.in locking/virtlockd.socket.in + +if WITH_LIBVIRTD +if LIBVIRT_INIT_SCRIPT_SYSTEMD + +SYSTEMD_UNIT_DIR = /lib/systemd/system + +BUILT_SOURCES += virtlockd.service virtlockd.socket + +install-systemd: virtlockd.init install-sysconfig + mkdir -p $(DESTDIR)$(SYSTEMD_UNIT_DIR)
$(MKDIR_P)
+ $(INSTALL_SCRIPT) virtlockd.service \ + $(DESTDIR)$(SYSTEMD_UNIT_DIR)/ + $(INSTALL_SCRIPT) virtlockd.socket \ + $(DESTDIR)$(SYSTEMD_UNIT_DIR)/
s/INSTALL_SCRIPT/INSTALL_DATA/2 - systemd files should not be marked executable (cf. install-init-systemd in daemon/Makefile.am).
+ +virtlockd.service: locking/virtlockd.service.in $(top_builddir)/config.status + $(AM_V_GEN)sed \ + -e "s!::localstatedir::!$(localstatedir)!g" \ + -e "s!::sbindir::!$(sbindir)!g" \ + -e "s!::sysconfdir::!$(sysconfdir)!g" \ + < $< > $@-t && \ + chmod a+x $@-t && \ + mv $@-t $@ +
Problem. Here, you look for '::sbindir::'...
+++ b/src/locking/lock_daemon.c @@ -454,8 +454,69 @@ virLockDaemonSetupSignals(virNetServerPtr srv) return 0; }
+ static int -virLockDaemonSetupNetworking(virNetServerPtr srv, const char *sock_path) +virLockDaemonSetupNetworkingSystemD(virNetServerPtr srv) +{
+ unsetenv("LISTEN_PID"); + unsetenv("LISTEN_FDS");
I was scratching my head on this one, until I realized you want to re-exec, and the re-exec uses our mechanisms rather than systemd mechanisms to reuse the fd.
+++ b/src/locking/virtlockd.service.in @@ -0,0 +1,13 @@ +[Unit] +Description=Virtual machine lock manager +Requires=virtlockd.socket +After=syslog.target + +[Service] +EnvironmentFile=-/etc/sysconfig/virtlockd +ExecStart=@sbindir@/virtlockd
...but here, you used @sbindir@.
+ExecReload=/bin/kill -HUP $MAINPID +# Loosing the locks is a really bad thing that will
s/Loosing/Losing/
+# cause the machine to be fenced (rebooted), so make +# sure we discourage OOM killer +OOMScoreAdjust=-900
Losing locks is bad, but does virtlockd really cause fencing on failure, or are you over-exaggerating the effects due to copying from something stricter? -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

From: "Daniel P. Berrange" <berrange@redhat.com> The virtlockd daemon maintains file locks on behalf of libvirtd and any VMs it is running. These file locks must be held for as long as any VM is running. If virtlockd itself ever quits, then it is expected that a node would be fenced/rebooted. Thus to allow for software upgrads on live systemd, virtlockd needs the ability to re-exec() itself. Upon receipt of SIGUSR1, virtlockd will save its current live state out to a file /var/run/virtlockd-restart-exec.json It then re-exec()'s itself with exactly the same argv as it originally had, and loads the state file, reconstructing any objects as appropriate. The state file contains information about all locks held and all network services and clients currently active. An example state document is { "server": { "min_workers": 1, "max_workers": 20, "priority_workers": 0, "max_clients": 20, "keepaliveInterval": 4294967295, "keepaliveCount": 0, "keepaliveRequired": false, "services": [ { "auth": 0, "readonly": false, "nrequests_client_max": 1, "socks": [ { "fd": 6, "errfd": -1, "pid": 0, "isClient": false } ] } ], "clients": [ { "auth": 0, "readonly": false, "nrequests_max": 1, "sock": { "fd": 9, "errfd": -1, "pid": 0, "isClient": true }, "privateData": { "restricted": true, "ownerPid": 1722, "ownerId": 6, "ownerName": "f18x86_64", "ownerUUID": "97586ba9-df27-9459-c806-f016c8bbd224" } }, { "auth": 0, "readonly": false, "nrequests_max": 1, "sock": { "fd": 10, "errfd": -1, "pid": 0, "isClient": true }, "privateData": { "restricted": true, "ownerPid": 1784, "ownerId": 7, "ownerName": "f16x86_64", "ownerUUID": "7b8e5e42-b875-61e9-b981-91ad8fa46979" } } ] }, "defaultLockspace": { "resources": [ { "name": "/var/lib/libvirt/images/f16x86_64.raw", "path": "/var/lib/libvirt/images/f16x86_64.raw", "fd": 14, "lockHeld": true, "flags": 0, "owners": [ 1784 ] }, { "name": "/var/lib/libvirt/images/shared.img", "path": "/var/lib/libvirt/images/shared.img", "fd": 12, "lockHeld": true, "flags": 1, "owners": [ 1722, 1784 ] }, { "name": "/var/lib/libvirt/images/f18x86_64.img", "path": "/var/lib/libvirt/images/f18x86_64.img", "fd": 11, "lockHeld": true, "flags": 0, "owners": [ 1722 ] } ] }, "lockspaces": [ ], "magic": "30199" } Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- libvirt.spec.in | 5 +- src/locking/lock_daemon.c | 417 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 408 insertions(+), 14 deletions(-) diff --git a/libvirt.spec.in b/libvirt.spec.in index 5a5d724..4dde964 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -1436,7 +1436,10 @@ fi /bin/systemctl daemon-reload >/dev/null 2>&1 || : if [ $1 -ge 1 ] ; then # Package upgrade, not uninstall - /bin/systemctl try-restart virtlockd.service >/dev/null 2>&1 || : + /bin/systemctl status virtlockd.service >/dev/null 2>&1 + if [ $? = 1 ] ; then + /bin/systemctl kill --signal=USR1 virtlockd.service >/dev/null 2>&1 || : + fi /bin/systemctl try-restart libvirtd.service >/dev/null 2>&1 || : fi %endif diff --git a/src/locking/lock_daemon.c b/src/locking/lock_daemon.c index 7bc6917..ab9ca35 100644 --- a/src/locking/lock_daemon.c +++ b/src/locking/lock_daemon.c @@ -42,6 +42,7 @@ #include "rpc/virnetserver.h" #include "virrandom.h" #include "virhash.h" +#include "uuid.h" #include "locking/lock_daemon_dispatch.h" #include "locking/lock_protocol.h" @@ -64,6 +65,7 @@ struct _virLockDaemon { virLockDaemonPtr lockDaemon = NULL; static bool privileged; +static bool execRestart = false; enum { VIR_LOCK_DAEMON_ERR_NONE = 0, @@ -97,6 +99,14 @@ virLockDaemonClientNew(virNetServerClientPtr client, static void virLockDaemonClientFree(void *opaque); +static void * +virLockDaemonClientNewPostExecRestart(virNetServerClientPtr client, + virJSONValuePtr object, + void *opaque); +static virJSONValuePtr +virLockDaemonClientPreExecRestart(virNetServerClientPtr client, + void *opaque); + static void virLockDaemonFree(virLockDaemonPtr lockd) { @@ -138,7 +148,7 @@ virLockDaemonNew(void) -1, 0, NULL, NULL, virLockDaemonClientNew, - NULL, + virLockDaemonClientPreExecRestart, virLockDaemonClientFree, NULL))) goto error; @@ -158,6 +168,90 @@ error: } +static virLockDaemonPtr +virLockDaemonNewPostExecRestart(virJSONValuePtr object) +{ + virLockDaemonPtr lockd; + virJSONValuePtr child; + virJSONValuePtr lockspaces; + size_t i; + int n; + + if (VIR_ALLOC(lockd) < 0) { + virReportOOMError(); + return NULL; + } + + if (virMutexInit(&lockd->lock) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Unable to initialize mutex")); + VIR_FREE(lockd); + return NULL; + } + + if (!(lockd->lockspaces = virHashCreate(3, + virLockDaemonLockSpaceDataFree))) + goto error; + + if (!(child = virJSONValueObjectGet(object, "defaultLockspace"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing defaultLockspace data from JSON file")); + goto error; + } + + if (!(lockd->defaultLockspace = + virLockSpaceNewPostExecRestart(child))) + goto error; + + if (!(lockspaces = virJSONValueObjectGet(object, "lockspaces"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing lockspaces data from JSON file")); + goto error; + } + + if ((n = virJSONValueArraySize(lockspaces)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Malformed lockspaces data from JSON file")); + goto error; + } + + for (i = 0 ; i < n ; i++) { + virLockSpacePtr lockspace; + + child = virJSONValueArrayGet(lockspaces, i); + + if (!(lockspace = virLockSpaceNewPostExecRestart(child))) + goto error; + + if (virHashAddEntry(lockd->lockspaces, + virLockSpaceGetDirectory(lockspace), + lockspace) < 0) { + virLockSpaceFree(lockspace); + } + } + + if (!(child = virJSONValueObjectGet(object, "server"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing server data from JSON file")); + goto error; + } + + if (!(lockd->srv = virNetServerNewPostExecRestart(child, + virLockDaemonClientNew, + virLockDaemonClientNewPostExecRestart, + virLockDaemonClientPreExecRestart, + virLockDaemonClientFree, + NULL))) + goto error; + + return lockd; + +error: + virLockDaemonFree(lockd); + return NULL; +} + + int virLockDaemonAddLockSpace(virLockDaemonPtr lockd, const char *path, virLockSpacePtr lockspace) @@ -442,6 +536,15 @@ virLockDaemonShutdownHandler(virNetServerPtr srv, virNetServerQuit(srv); } +static void +virLockDaemonExecRestartHandler(virNetServerPtr srv, + siginfo_t *sig ATTRIBUTE_UNUSED, + void *opaque ATTRIBUTE_UNUSED) +{ + execRestart = true; + virNetServerQuit(srv); +} + static int virLockDaemonSetupSignals(virNetServerPtr srv) { @@ -451,6 +554,8 @@ virLockDaemonSetupSignals(virNetServerPtr srv) return -1; if (virNetServerAddSignalHandler(srv, SIGTERM, virLockDaemonShutdownHandler, NULL) < 0) return -1; + if (virNetServerAddSignalHandler(srv, SIGUSR1, virLockDaemonExecRestartHandler, NULL) < 0) + return -1; return 0; } @@ -464,6 +569,8 @@ virLockDaemonSetupNetworkingSystemD(virNetServerPtr srv) unsigned long long procid; unsigned int nfds; + VIR_DEBUG("Setting up networking from systemd"); + if (!(pidstr = getenv("LISTEN_PID"))) { VIR_DEBUG("No LISTEN_FDS from systemd"); return 0; @@ -674,6 +781,277 @@ error: } +static void * +virLockDaemonClientNewPostExecRestart(virNetServerClientPtr client, + virJSONValuePtr object, + void *opaque) +{ + virLockDaemonClientPtr priv = virLockDaemonClientNew(client, opaque); + unsigned int ownerPid; + const char *ownerUUID; + const char *ownerName; + + if (!priv) + return NULL; + + if (virJSONValueObjectGetBoolean(object, "restricted", &priv->restricted) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing restricted data in JSON document")); + goto error; + } + if (virJSONValueObjectGetNumberUint(object, "ownerPid", &ownerPid) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing ownerPid data in JSON document")); + goto error; + } + priv->ownerPid = (pid_t)ownerPid; + if (virJSONValueObjectGetNumberUint(object, "ownerId", &priv->ownerId) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing ownerId data in JSON document")); + goto error; + } + if (!(ownerName = virJSONValueObjectGetString(object, "ownerName"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing ownerName data in JSON document")); + goto error; + } + if (!(priv->ownerName = strdup(ownerName))) { + virReportOOMError(); + goto error; + } + if (!(ownerUUID = virJSONValueObjectGetString(object, "ownerUUID"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing ownerUUID data in JSON document")); + goto error; + } + if (virUUIDParse(ownerUUID, priv->ownerUUID) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing ownerUUID data in JSON document")); + goto error; + } + return priv; + +error: + virLockDaemonClientFree(priv); + return NULL; +} + + +static virJSONValuePtr +virLockDaemonClientPreExecRestart(virNetServerClientPtr client ATTRIBUTE_UNUSED, + void *opaque) +{ + virLockDaemonClientPtr priv = opaque; + virJSONValuePtr object = virJSONValueNewObject(); + char uuidstr[VIR_UUID_STRING_BUFLEN]; + + if (!object) + return NULL; + + if (virJSONValueObjectAppendBoolean(object, "restricted", priv->restricted) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Cannot set restricted data in JSON document")); + goto error; + } + if (virJSONValueObjectAppendNumberUint(object, "ownerPid", priv->ownerPid) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Cannot set ownerPid data in JSON document")); + goto error; + } + if (virJSONValueObjectAppendNumberUint(object, "ownerId", priv->ownerId) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Cannot set ownerId data in JSON document")); + goto error; + } + if (virJSONValueObjectAppendString(object, "ownerName", priv->ownerName) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Cannot set ownerName data in JSON document")); + goto error; + } + virUUIDFormat(priv->ownerUUID, uuidstr); + if (virJSONValueObjectAppendString(object, "ownerUUID", uuidstr) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Cannot set ownerUUID data in JSON document")); + goto error; + } + + return object; + +error: + virJSONValueFree(object); + return NULL; +} + + +#define VIR_LOCK_DAEMON_RESTART_EXEC_FILE LOCALSTATEDIR "/run/virtlockd-restart-exec.json" + +static char * +virLockDaemonGetExecRestartMagic(void) +{ + char *ret; + + if (virAsprintf(&ret, "%lld", + (long long int)getpid()) < 0) { + virReportOOMError(); + return NULL; + } + + return ret; +} + + +static int +virLockDaemonPostExecRestart(void) +{ + const char *gotmagic; + char *wantmagic = NULL; + int ret = -1; + char *state = NULL; + virJSONValuePtr object = NULL; + + VIR_DEBUG("Running post-restart exec"); + + if (!virFileExists(VIR_LOCK_DAEMON_RESTART_EXEC_FILE)) { + VIR_DEBUG("No restart file %s present", + VIR_LOCK_DAEMON_RESTART_EXEC_FILE); + ret = 0; + goto cleanup; + } + + if (virFileReadAll(VIR_LOCK_DAEMON_RESTART_EXEC_FILE, + 1024 * 1024 * 10, /* 10 MB */ + &state) < 0) + goto cleanup; + + VIR_DEBUG("Loading state %s", state); + + if (!(object = virJSONValueFromString(state))) + goto cleanup; + + gotmagic = virJSONValueObjectGetString(object, "magic"); + if (!gotmagic) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing magic data in JSON document")); + goto cleanup; + } + + if (!(wantmagic = virLockDaemonGetExecRestartMagic())) + goto cleanup; + + if (STRNEQ(gotmagic, wantmagic)) { + VIR_WARN("Found restart exec file with old magic %s vs wanted %s", + gotmagic, wantmagic); + ret = 0; + goto cleanup; + } + + if (!(lockDaemon = virLockDaemonNewPostExecRestart(object))) + goto cleanup; + + ret = 1; + +cleanup: + unlink(VIR_LOCK_DAEMON_RESTART_EXEC_FILE); + VIR_FREE(wantmagic); + VIR_FREE(state); + virJSONValueFree(object); + return ret; +} + + +static int +virLockDaemonPreExecRestart(virNetServerPtr srv, + char **argv) +{ + virJSONValuePtr child; + char *state = NULL; + int ret = -1; + virJSONValuePtr object; + char *magic; + virHashKeyValuePairPtr pairs = NULL, tmp; + virJSONValuePtr lockspaces; + + VIR_DEBUG("Running pre-restart exec"); + + if (!(object = virJSONValueNewObject())) + goto cleanup; + + if (!(child = virNetServerPreExecRestart(srv))) + goto cleanup; + + if (virJSONValueObjectAppend(object, "server", child) < 0) { + virJSONValueFree(child); + goto cleanup; + } + + if (!(child = virLockSpacePreExecRestart(lockDaemon->defaultLockspace))) + goto cleanup; + + if (virJSONValueObjectAppend(object, "defaultLockspace", child) < 0) { + virJSONValueFree(child); + goto cleanup; + } + + if (!(lockspaces = virJSONValueNewArray())) + goto cleanup; + if (virJSONValueObjectAppend(object, "lockspaces", lockspaces) < 0) { + virJSONValueFree(lockspaces); + goto cleanup; + } + + + tmp = pairs = virHashGetItems(lockDaemon->lockspaces, NULL); + while (tmp && tmp->key) { + virLockSpacePtr lockspace = (virLockSpacePtr)tmp->value; + + if (!(child = virLockSpacePreExecRestart(lockspace))) + goto cleanup; + + if (virJSONValueArrayAppend(lockspaces, child) < 0) { + virJSONValueFree(child); + goto cleanup; + } + + tmp++; + } + + if (!(magic = virLockDaemonGetExecRestartMagic())) + goto cleanup; + + if (virJSONValueObjectAppendString(object, "magic", magic) < 0) { + VIR_FREE(magic); + goto cleanup; + } + + if (!(state = virJSONValueToString(object, true))) + goto cleanup; + + VIR_DEBUG("Saving state %s", state); + + if (virFileWriteStr(VIR_LOCK_DAEMON_RESTART_EXEC_FILE, + state, 0700) < 0) { + virReportSystemError(errno, + _("Unable to save state file %s"), + VIR_LOCK_DAEMON_RESTART_EXEC_FILE); + goto cleanup; + } + + if (execv(argv[0], argv) < 0) { + virReportSystemError(errno, "%s", + _("Unable to restart self")); + goto cleanup; + } + + abort(); /* This should be impossible to reach */ + +cleanup: + VIR_FREE(pairs); + VIR_FREE(state); + virJSONValueFree(object); + return ret; +} + + static void virLockDaemonUsage(const char *argv0) { @@ -866,22 +1244,30 @@ int main(int argc, char **argv) { goto cleanup; } - - if (!(lockDaemon = virLockDaemonNew())) { + if ((rv = virLockDaemonPostExecRestart()) < 0) { ret = VIR_LOCK_DAEMON_ERR_INIT; goto cleanup; } - if ((rv = virLockDaemonSetupNetworkingSystemD(lockDaemon->srv)) < 0) { - ret = VIR_LOCK_DAEMON_ERR_NETWORK; - goto cleanup; - } + /* rv == 1, means we setup everything from saved state, + * so we only setup stuff from scratch if rv == 0 */ + if (rv == 0) { + if (!(lockDaemon = virLockDaemonNew())) { + ret = VIR_LOCK_DAEMON_ERR_INIT; + goto cleanup; + } - /* Only do this, if systemd did not pass a FD */ - if (rv == 0 && - virLockDaemonSetupNetworkingNative(lockDaemon->srv, sock_file) < 0) { - ret = VIR_LOCK_DAEMON_ERR_NETWORK; - goto cleanup; + if ((rv = virLockDaemonSetupNetworkingSystemD(lockDaemon->srv)) < 0) { + ret = VIR_LOCK_DAEMON_ERR_NETWORK; + goto cleanup; + } + + /* Only do this, if systemd did not pass a FD */ + if (rv == 0 && + virLockDaemonSetupNetworkingNative(lockDaemon->srv, sock_file) < 0) { + ret = VIR_LOCK_DAEMON_ERR_NETWORK; + goto cleanup; + } } if ((virLockDaemonSetupSignals(lockDaemon->srv)) < 0) { @@ -921,7 +1307,12 @@ int main(int argc, char **argv) { virNetServerUpdateServices(lockDaemon->srv, true); virNetServerRun(lockDaemon->srv); - ret = 0; + + if (execRestart && + virLockDaemonPreExecRestart(lockDaemon->srv, argv) < 0) + ret = -1; + else + ret = 0; cleanup: virObjectUnref(lockProgram); -- 1.7.11.2

On 08/09/2012 09:20 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
The virtlockd daemon maintains file locks on behalf of libvirtd and any VMs it is running. These file locks must be held for as long as any VM is running. If virtlockd itself ever quits, then it is expected that a node would be fenced/rebooted. Thus to allow for software upgrads on live systemd, virtlockd needs the
s/upgrads/upgrades/ s/systemd/systems/ ?
ability to re-exec() itself.
Upon receipt of SIGUSR1, virtlockd will save its current live state out to a file /var/run/virtlockd-restart-exec.json It then re-exec()'s itself with exactly the same argv as it originally had, and loads the state file, reconstructing any objects as appropriate.
The state file contains information about all locks held and all network services and clients currently active. An example state document is
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- libvirt.spec.in | 5 +- src/locking/lock_daemon.c | 417 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 408 insertions(+), 14 deletions(-)
diff --git a/libvirt.spec.in b/libvirt.spec.in index 5a5d724..4dde964 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -1436,7 +1436,10 @@ fi /bin/systemctl daemon-reload >/dev/null 2>&1 || : if [ $1 -ge 1 ] ; then # Package upgrade, not uninstall - /bin/systemctl try-restart virtlockd.service >/dev/null 2>&1 || : + /bin/systemctl status virtlockd.service >/dev/null 2>&1 + if [ $? = 1 ] ; then
That says 'if virtlockd.service failed to report status, then send it SIGUSR1'. Don't you really want to check '$? = 0'?
+++ b/src/locking/lock_daemon.c
+static virLockDaemonPtr +virLockDaemonNewPostExecRestart(virJSONValuePtr object) +{ + virLockDaemonPtr lockd; + virJSONValuePtr child; + virJSONValuePtr lockspaces; + size_t i; + int n; + + if (VIR_ALLOC(lockd) < 0) { + virReportOOMError(); + return NULL; + } + + if (virMutexInit(&lockd->lock) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Unable to initialize mutex")); + VIR_FREE(lockd); + return NULL; + } + + if (!(lockd->lockspaces = virHashCreate(3, + virLockDaemonLockSpaceDataFree)))
Rather than re-creating with the original default of 3, should we recreate with the number of elements read from JSON? (Of course, that means you can't call virHashCreate until later in the function, which may be too complex to wait for)
@@ -464,6 +569,8 @@ virLockDaemonSetupNetworkingSystemD(virNetServerPtr srv) unsigned long long procid; unsigned int nfds;
+ VIR_DEBUG("Setting up networking from systemd"); +
This hunk belongs in the previous patch.
+ if (virJSONValueObjectGetNumberUint(object, "ownerPid", &ownerPid) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing ownerPid data in JSON document")); + goto error; + } + priv->ownerPid = (pid_t)ownerPid;
This cast to (pid_t) might result in truncation. Should we be doing further sanity checking, such as ensuring the cast doesn't truncate and that the resulting pid is non-negative?
+static int +virLockDaemonPreExecRestart(virNetServerPtr srv, + char **argv) +{ + virJSONValuePtr child; + char *state = NULL; + int ret = -1; + virJSONValuePtr object; + char *magic; + virHashKeyValuePairPtr pairs = NULL, tmp; + virJSONValuePtr lockspaces; + + VIR_DEBUG("Running pre-restart exec");
Oh - now I see something that I wasn't catching when I complained about the earlier patches dealing with a fork/CLOEXEC/exec timing - you _aren't_ forking, but directly calling exec to overlay the current process with the new binary! Furthermore, although it looks like virtlockd calls fork to daemonize at startup, it never spawns any child processes - so we _don't_ have to worry about any O_CLOEXEC races on creation, or restoring CLOEXEC on restart, precisely because we never spawn off a child in another thread where the leak would be problematic. Still, I would feel a lot better if we document this clearly in the code (maybe 'this function is only safe to call from virtlockd' for all PreExec functions that clear CLOEXEC).
+ VIR_DEBUG("Saving state %s", state); + + if (virFileWriteStr(VIR_LOCK_DAEMON_RESTART_EXEC_FILE, + state, 0700) < 0) {
Shouldn't 0600 be sufficient? We don't need to execute the .json file. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

From: "Daniel P. Berrange" <berrange@redhat.com> This adds a 'lockd' lock driver which is just a client which talks to the lockd daemon to perform all locking. This will be the default lock driver for any hypervisor which needs one. * src/Makefile.am: Add lockd.so plugin * src/locking/lock_driver_lockd.c: Lockd driver impl Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- po/POTFILES.in | 1 + src/Makefile.am | 26 +- src/locking/lock_driver_lockd.c | 561 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 584 insertions(+), 4 deletions(-) create mode 100644 src/locking/lock_driver_lockd.c diff --git a/po/POTFILES.in b/po/POTFILES.in index 6fe9690..618267b 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -42,6 +42,7 @@ src/libvirt.c src/libvirt-qemu.c src/locking/lock_daemon.c src/locking/lock_daemon_dispatch.c +src/locking/lock_driver_lockd.c src/locking/lock_driver_sanlock.c src/locking/lock_manager.c src/lxc/lxc_cgroup.c diff --git a/src/Makefile.am b/src/Makefile.am index 35e8338..777ce07 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -156,6 +156,10 @@ LOCK_DAEMON_GENERATED = \ BUILT_SOURCES += $(LOCK_DAEMON_GENERATED) MAINTAINERCLEANFILES += $(LOCK_DAEMON_GENERATED) +LOCK_DRIVER_LOCKD_SOURCES = \ + locking/lock_driver_lockd.c \ + $(NULL) + LOCK_DAEMON_SOURCES = \ locking/lock_daemon.h \ locking/lock_daemon.c \ @@ -1478,7 +1482,22 @@ libvirt_qemu_la_CFLAGS = $(AM_CFLAGS) libvirt_qemu_la_LIBADD = libvirt.la $(CYGWIN_EXTRA_LIBADD) EXTRA_DIST += $(LIBVIRT_QEMU_SYMBOL_FILE) +lockdriverdir = $(libdir)/libvirt/lock-driver +lockdriver_LTLIBRARIES = + if WITH_LIBVIRTD +lockdriver_LTLIBRARIES += lockd.la +lockd_la_SOURCES = \ + $(LOCK_DRIVER_LOCKD_SOURCES) \ + $(LOCK_PROTOCOL_GENERATED) \ + $(NULL) +lockd_la_CFLAGS = $(AM_CFLAGS) +lockd_la_LDFLAGS = -module -avoid-version +lockd_la_LIBADD = ../gnulib/lib/libgnu.la libvirt-net-rpc.la libvirt-net-rpc-client.la +if WITH_DTRACE_PROBES +lockd_la_LIBADD += libvirt_probes.lo +endif + sbin_PROGRAMS = virtlockd virtlockd_SOURCES = \ @@ -1506,7 +1525,8 @@ virtlockd_LDADD += libvirt_probes.lo endif else -EXTRA_DIST += $(LOCK_DAEMON_SOURCES) +EXTRA_DIST += $(LOCK_DAEMON_SOURCES) \ + $(LOCK_DRIVER_LOCKD_SOURCES) endif EXTRA_DIST += locking/virtlockd.sysconf @@ -1600,9 +1620,7 @@ virtlockd.socket: locking/virtlockd.socket.in $(top_builddir)/config.status if HAVE_SANLOCK -lockdriverdir = $(libdir)/libvirt/lock-driver -lockdriver_LTLIBRARIES = sanlock.la - +lockdriver_LTLIBRARIES += sanlock.la sanlock_la_SOURCES = $(LOCK_DRIVER_SANLOCK_SOURCES) sanlock_la_CFLAGS = $(AM_CFLAGS) sanlock_la_LDFLAGS = -module -avoid-version diff --git a/src/locking/lock_driver_lockd.c b/src/locking/lock_driver_lockd.c new file mode 100644 index 0000000..462996b --- /dev/null +++ b/src/locking/lock_driver_lockd.c @@ -0,0 +1,561 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + */ + +#include <config.h> + +#include "lock_driver.h" +#include "memory.h" +#include "logging.h" +#include "uuid.h" +#include "util.h" +#include "virfile.h" +#include "virterror_internal.h" +#include "rpc/virnetclient.h" +#include "lock_protocol.h" +#include "configmake.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; + +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; +}; + + +#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 virLockManagerLockDaemonInit(unsigned int version, + const char *configFile, + unsigned int flags) +{ + VIR_DEBUG("version=%u configFile=%s flags=%x", version, NULLSTR(configFile), flags); + + return 0; +} + +static int virLockManagerLockDaemonDeinit(void) +{ + VIR_DEBUG(" "); + + 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 char *virLockManagerLockDaemonPath(bool privileged) +{ + char *path; + if (privileged) { + if (!(path = strdup(LOCALSTATEDIR "/run/libvirt/virtlockd/virtlockd.sock"))) { + virReportOOMError(); + return NULL; + } + } else { + char *userdir; + if (!(userdir = virGetUserDirectory())) + return NULL; + + if (virAsprintf(&path, "%s/.libvirt/virtlockd/virtlockd.sock", userdir) < 0) { + virReportOOMError(); + } + VIR_FREE(userdir); + } + return path; +} + + +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 int virLockManagerLockDaemonAddResource(virLockManagerPtr lock, + unsigned int type, + const char *name, + size_t nparams, + virLockManagerParamPtr params, + unsigned int flags) +{ + virLockManagerLockDaemonPrivatePtr priv = lock->privateData; + char *newName; + char *newLockspace = NULL; + + 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 (!(newLockspace = strdup(""))) { + virReportOOMError(); + return -1; + } + 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; + } + } break; + default: + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unknown lock manager object type %d"), + type); + return -1; + } + + if (!(newName = strdup(name))) + goto no_memory; + + 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; + + return 0; + +no_memory: + virReportOOMError(); + VIR_FREE(newName); + return -1; +} + + +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 virLockManagerLockDaemonAcquire(virLockManagerPtr lock, + const char *state ATTRIBUTE_UNUSED, + unsigned int flags, + 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 (!(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; + virLockSpaceProtocolReleaseResourceArgs args; + int rv = -1; + + memset(&args, 0, sizeof(args)); + + if (state) + *state = NULL; + + if (!(client = virLockManagerLockDaemonConnect(lock, &program, &counter))) + goto cleanup; + + args.flags = flags; + + 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, +}; -- 1.7.11.2

On 08/09/2012 09:20 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
This adds a 'lockd' lock driver which is just a client which talks to the lockd daemon to perform all locking. This will be the default lock driver for any hypervisor which needs one.
* src/Makefile.am: Add lockd.so plugin * src/locking/lock_driver_lockd.c: Lockd driver impl
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- po/POTFILES.in | 1 + src/Makefile.am | 26 +- src/locking/lock_driver_lockd.c | 561 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 584 insertions(+), 4 deletions(-) create mode 100644 src/locking/lock_driver_lockd.c +++ b/src/locking/lock_driver_lockd.c @@ -0,0 +1,561 @@ +/* + * lock_driver_lockd.c: A lock driver which locks nothing + * + * Copyright (C) 2010-2011 Red Hat, Inc.
2012
+ +#define VIR_FROM_THIS VIR_FROM_LOCKING + +#define virLockError(code, ...) \ + virReportErrorHelper(VIR_FROM_THIS, code, __FILE__, \ + __FUNCTION__, __LINE__, __VA_ARGS__)
Rebase this out.
+static const char * +virLockManagerLockDaemonFindDaemon(void) +{ + const char *customDaemon = getenv("VIRTLOCKD_PATH"); + + if (customDaemon) + return customDaemon;
Is this safe even in the presence of another app linking in libvirt.so and modifying its own environment? It seems like we might want to strdup() that into safe static storage to isolate ourselves from future putenv() nonsense.
+static int virLockManagerLockDaemonDeinit(void) +{ + VIR_DEBUG(" ");
Is the empty space just so that you can trace that the function was reached?
+virLockDriver virLockDriverImpl = +{ + .version = VIR_LOCK_MANAGER_VERSION, + .flags = 0, + + .drvInit = virLockManagerLockDaemonInit, + .drvDeinit = virLockManagerLockDaemonDeinit,
Is it worth any /* since 0.10.0 */ comments, in case we later add to this struct, to know when each callback supported which lock manager features? -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

From: "Daniel P. Berrange" <berrange@redhat.com> The current default QEMU lock manager is the 'nop' lock manager, which obviously does not perform any locking. The new virtlockd daemon is able to work out of the box with zero configuration in single-host only mode. Enable this as the default lock manager for all QEMU guests * src/qemu/qemu.conf: Update docs for lock_driver parameter * src/qemu/qemu_conf.c: Change default lock manager to 'lockd' Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/qemu/qemu.conf | 17 ++++++++++------- src/qemu/qemu_conf.c | 2 +- src/qemu/test_libvirtd_qemu.aug.in | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/qemu/qemu.conf b/src/qemu/qemu.conf index ed4683c..b232762 100644 --- a/src/qemu/qemu.conf +++ b/src/qemu/qemu.conf @@ -318,14 +318,17 @@ # #allow_disk_format_probing = 1 - -# To enable 'Sanlock' project based locking of the file -# content (to prevent two VMs writing to the same -# disk), uncomment this +# By default the QEMU driver talks to the 'virtlockd' +# daemon to acquire exclusive locks for all guest disk +# images associated with a running VM. # -#lock_manager = "sanlock" - - +# To revert to behaviour of previous releases which did +# not acquire any locks, set this to 'nop'. +# +# To enable use of the external 'sanlock' locking +# daemon, change this to 'sanlock'. +# +#lock_manager = "lockd" # Set limit of maximum APIs queued on one domain. All other APIs # over this threshold will fail on acquiring job lock. Specially, diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index b7db277..753e87d 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -119,7 +119,7 @@ int qemudLoadDriverConfig(struct qemud_driver *driver, #endif if (!(driver->lockManager = - virLockManagerPluginNew("nop", NULL, 0))) + virLockManagerPluginNew("lockd", NULL, 0))) return -1; driver->keepAliveInterval = 5; diff --git a/src/qemu/test_libvirtd_qemu.aug.in b/src/qemu/test_libvirtd_qemu.aug.in index 959f250..8be6d07 100644 --- a/src/qemu/test_libvirtd_qemu.aug.in +++ b/src/qemu/test_libvirtd_qemu.aug.in @@ -54,7 +54,7 @@ module Test_libvirtd_qemu = { "mac_filter" = "1" } { "relaxed_acs_check" = "1" } { "allow_disk_format_probing" = "1" } -{ "lock_manager" = "sanlock" } +{ "lock_manager" = "lockd" } { "max_queued" = "0" } { "keepalive_interval" = "5" } { "keepalive_count" = "5" } -- 1.7.11.2

On 08/09/2012 09:20 AM, Daniel P. Berrange wrote:
From: "Daniel P. Berrange" <berrange@redhat.com>
The current default QEMU lock manager is the 'nop' lock manager, which obviously does not perform any locking. The new virtlockd daemon is able to work out of the box with zero configuration in single-host only mode. Enable this as the default lock manager for all QEMU guests
Question - can virtlockd also be made to work in multi-host mode for shared file systems that support fcntl locking, possibly by adding some non-default configuration?
* src/qemu/qemu.conf: Update docs for lock_driver parameter * src/qemu/qemu_conf.c: Change default lock manager to 'lockd'
Signed-off-by: Daniel P. Berrange <berrange@redhat.com> --- src/qemu/qemu.conf | 17 ++++++++++------- src/qemu/qemu_conf.c | 2 +- src/qemu/test_libvirtd_qemu.aug.in | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-)
Missing documentation in docs/locking.html.in on how to use virtlockd instead of sanlock, and any tips on setting up multi-box environments (if those are indeed possible).
diff --git a/src/qemu/qemu.conf b/src/qemu/qemu.conf index ed4683c..b232762 100644 --- a/src/qemu/qemu.conf +++ b/src/qemu/qemu.conf @@ -318,14 +318,17 @@ # #allow_disk_format_probing = 1
- -# To enable 'Sanlock' project based locking of the file -# content (to prevent two VMs writing to the same -# disk), uncomment this +# By default the QEMU driver talks to the 'virtlockd'
Is the config setting 'virtlockd'...
+# daemon to acquire exclusive locks for all guest disk +# images associated with a running VM. # -#lock_manager = "sanlock" - - +# To revert to behaviour of previous releases which did +# not acquire any locks, set this to 'nop'. +# +# To enable use of the external 'sanlock' locking +# daemon, change this to 'sanlock'. +# +#lock_manager = "lockd"
...or 'lockd'? This needs to be consistent. Maybe call out: This defaults to 'lockd' to use the virtlockd(8) daemon... Hmm, that means an earlier patch is also missing a virtlockd.8 man page. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On 08/09/2012 09:20 AM, Daniel P. Berrange wrote:
This is a long overdue update to a patch series I posted about a year ago
https://www.redhat.com/archives/libvir-list/2011-July/msg00337.html
There have been some major changes since that series
Getting closer! I think the overall design is sound, and I have now completed reviews of the entire series (instead of getting stuck at the beginning); looking forward to a final rebase to clear up the questions I raised on this round of review.
Still todo
- Add ability to quiesce all server/client I/O when doing re-exec()
- Add ability to save/restore data in any virNetMessagePtr structs in the client rx or tx queues
- Add ability to use custom lockspaces for LVM and SCSI/ISCSI block devices, instead of locking based on path, to gain cross-node safety, instead of node-local safety.
NB, the current re-exec() support works, but is not race safe without those first 2 todo items being completed
Yeah, but it's still a strict improvement over the 'nop' manager for out-of-the-box experience, so I don't mind if we push this series for 0.10.0 even without those to-do items resolved yet. I think the design is extensible enough that we aren't locking ourselves into an inability to implement them. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org
participants (2)
-
Daniel P. Berrange
-
Eric Blake