On 01/27/2012 11:21 PM, KAMEZAWA Hiroyuki wrote:
remote protocol driver for virDomainGetCPUStats()
Note:
Unlike other users of virTypedParameter with RPC, this interface
can return zero-filled entries because the interface assumes
2 dimentional array. Then, the function has its own serialize/deserialize
s/dimentional/dimensional/
routine.
daemon/remote.c | 146 +++++++++++++++++++++++++++++++++++++++++++
src/remote/remote_driver.c | 117 ++++++++++++++++++++++++++++++++++
src/remote/remote_protocol.x | 22 ++++++
src/remote_protocol-structs | 15 ++++
---
daemon/remote.c | 147 ++++++++++++++++++++++++++++++++++++++++++
src/remote/remote_driver.c | 117 +++++++++++++++++++++++++++++++++
src/remote/remote_protocol.x | 22 ++++++-
src/remote_protocol-structs | 15 +++
Odd that the diffstat listed twice.
I'm going to reorder my review, to start with the .x file.
diff --git a/src/remote/remote_protocol.x
b/src/remote/remote_protocol.x
index 0f354bb..205aeeb 100644
--- a/src/remote/remote_protocol.x
+++ b/src/remote/remote_protocol.x
@@ -208,6 +208,12 @@ const REMOTE_DOMAIN_SEND_KEY_MAX = 16;
*/
const REMOTE_DOMAIN_INTERFACE_PARAMETERS_MAX = 16;
+/*
+ * Upper limit on list of (real) cpu parameters
+ * nparams(<=16) * ncpus(<=128) <= 2048.
+ */
+const REMOTE_DOMAIN_GET_CPU_STATS_MAX = 2048;
We should enforce the two limits independently, which means we also need
two smaller constants (it's a shame that rpcgen doesn't allow products
when computing a constant, but we can document how we arrive at various
numbers). We can reuse VIR_NODE_CPU_STATS_MAX for one of the two limits.
+
/* UUID. VIR_UUID_BUFLEN definition comes from libvirt.h */
typedef opaque remote_uuid[VIR_UUID_BUFLEN];
@@ -1149,6 +1155,19 @@ struct remote_domain_get_block_io_tune_ret {
int nparams;
};
+struct remote_domain_get_cpu_stats_args {
+ remote_nonnull_domain dom;
+ unsigned int nparams;
+ int start_cpu;
+ unsigned int ncpus;
+ unsigned int flags;
+};
+
+struct remote_domain_get_cpu_stats_ret {
+ remote_typed_param params<REMOTE_DOMAIN_GET_CPU_STATS_MAX>;
+ int nparams;
+};
Looks good.
+
/* Network calls: */
struct remote_num_of_networks_ret {
@@ -2667,7 +2686,8 @@ enum remote_procedure {
REMOTE_PROC_DOMAIN_SET_INTERFACE_PARAMETERS = 256, /* autogen
autogen */
REMOTE_PROC_DOMAIN_GET_INTERFACE_PARAMETERS = 257, /* skipgen
skipgen */
REMOTE_PROC_DOMAIN_SHUTDOWN_FLAGS = 258, /* autogen autogen */
- REMOTE_PROC_STORAGE_VOL_WIPE_PATTERN = 259 /* autogen autogen */
+ REMOTE_PROC_STORAGE_VOL_WIPE_PATTERN = 259, /* autogen autogen */
+ REMOTE_PROC_DOMAIN_GET_CPU_STATS = 260 /* skipgen skipgen */
Trivial conflict resolution.
+static int
+remoteDispatchDomainGetCPUStats(virNetServerPtr server ATTRIBUTE_UNUSED,
+ virNetServerClientPtr client ATTRIBUTE_UNUSED,
+ virNetMessagePtr hdr ATTRIBUTE_UNUSED,
+ virNetMessageErrorPtr rerr,
+ remote_domain_get_cpu_stats_args *args,
+ remote_domain_get_cpu_stats_ret *ret)
+{
+ virDomainPtr dom = NULL;
+ struct daemonClientPrivate *priv;
+ virTypedParameterPtr params = NULL;
+ remote_typed_param *info = NULL;
+ int cpu, idx, src, dest;
+ int rv = -1;
+ unsigned int return_size = 0;
+ int percpu_len = 0;
+
+ priv = virNetServerClientGetPrivateData(client);
+ if (!priv->conn) {
+ virNetError(VIR_ERR_INTERNAL_ERROR, "%s", _("connection not
open"));
+ goto cleanup;
+ }
+
+ /*
+ * nparams * ncpus should be smaller than
+ * REMOTE_DOMAIN_GET_CPU_STATS_MAX but nparams
+ * and ncpus are limited by API. check it.
+ */
+ if (args->nparams > 16) {
+ virNetError(VIR_ERR_INTERNAL_ERROR, "%s", _("nparams too
large"));
+ goto cleanup;
+ }
+ if (args->ncpus > 128) {
Use the constants from the .x file, rather than open-coding things.
Oops, I just realized that I mentioned that libvirt.c should not have a
length check, but then I forgot to delete it. Meanwhile, even if
libvirt.c does not have a length check, it still needs to have a
multiplication overflow check.
+ /*
+ * Here, we return values based on the real param size returned by
+ * a driver rather than the passed one. RPC Client stub should decode
+ * it to fit callar's expectation.
s/callar/caller/
+ *
+ * If cpu ID is sparse, The whole array for some cpu IDs will be
+ * zero-cleared. This doesn't meet to remoteSerializeTypedParameters()
+ * and we do serialization by ourselves.
We can still use remoteSerializeTypedParameters; we just have to teach
it to skip zero entries like it already can skip string entries.
Oh, I just realized that to support string entries, we have to support a
flags of VIR_TYPED_PARAM_STRING_OKAY.
+success:
+ rv = 0;
+ ret->params.params_len = return_size;
+ ret->params.params_val = info;
+ ret->nparams = percpu_len;
+cleanup:
+ if (rv < 0) {
+ virNetMessageSaveError(rerr);
+ if (info) {
Cleanup is a lot simpler if we let the existing serialization skip zero
entries.
+++ b/src/remote/remote_driver.c
@@ -2305,6 +2305,122 @@ done:
return rv;
}
+static int remoteDomainGetCPUStats(virDomainPtr domain,
+ virTypedParameterPtr params,
+ unsigned int nparams,
+ int start_cpu,
+ unsigned int ncpus,
+ unsigned int flags)
+{
+ struct private_data *priv = domain->conn->privateData;
+ remote_domain_get_cpu_stats_args args;
+ remote_domain_get_cpu_stats_ret ret;
+ remote_typed_param *info;
+ int rv = -1;
+ int cpu, idx, src, dest;
+
+ remoteDriverLock(priv);
+
+ make_nonnull_domain(&args.dom, domain);
+ args.nparams = nparams;
+ args.start_cpu = start_cpu;
+ args.ncpus = ncpus;
+ args.flags = flags;
+
+ memset(&ret, 0, sizeof(ret));
This side needs to validate incoming parameters before sending over the
wire. Hmm, remoteNodeGetCPUStats needs to do likewise.
+
+ if (call(domain->conn, priv, 0, REMOTE_PROC_DOMAIN_GET_CPU_STATS,
+ (xdrproc_t) xdr_remote_domain_get_cpu_stats_args,
+ (char *) &args,
+ (xdrproc_t) xdr_remote_domain_get_cpu_stats_ret,
+ (char *) &ret) == -1)
+ goto done;
+
+ if (nparams == 0) {
+ rv = ret.nparams;
+ goto cleanup;
+ }
+ /*
+ * the returned arrray's size is not same to nparams * ncpus. And
+ * if cpu ID is not contiguous, all-zero entries can be found.
+ */
+ memset(params, 0, sizeof(virTypedParameter) * nparams * ncpus);
I prefer sizeof(expr) over sizeof(type), in case expr ever changes types.
+
+ /* Here, ret.nparams is always smaller than nparams */
+ info = ret.params.params_val;
+
+ for (cpu = 0; cpu < ncpus; ++cpu) {
+ for (idx = 0; idx < ret.nparams; ++idx) {
+ src = cpu * ret.nparams + idx;
+ dest = cpu * nparams + idx;
+
+ if (info[src].value.type == 0) /* skip zeroed ones */
+ continue;
Again, open coding things is not nice. If we write the server to never
send zero entries (which is a good thing - less data over the wire),
then the client needs to re-create zero entries by calling
deserialization in a loop - deserialize ret.nparams entries at every
nparams slot.
+
+ rv = ret.nparams;
+cleanup:
+ if (rv < 0) {
+ int max = nparams * ncpus;
+ int i;
+
+ for (i = 0; i < max; i++) {
+ if (params[i].type == VIR_TYPED_PARAM_STRING)
+ VIR_FREE(params[i].value.s);
Potential free of bogus memory if anything can reach cleanup with rv < 0
but before we sanitized the contents of params.
Here's what I'm squashing in:
diff --git i/daemon/remote.c w/daemon/remote.c
index 15857a8..cb8423a 100644
--- i/daemon/remote.c
+++ w/daemon/remote.c
@@ -704,8 +704,11 @@ remoteSerializeTypedParameters(virTypedParameterPtr
params,
}
for (i = 0, j = 0; i < nparams; ++i) {
- if (!(flags & VIR_TYPED_PARAM_STRING_OKAY) &&
- params[i].type == VIR_TYPED_PARAM_STRING) {
+ /* virDomainGetCPUStats can return a sparse array; also, we
+ * can't pass back strings to older clients. */
+ if (!params[i].type ||
+ (!(flags & VIR_TYPED_PARAM_STRING_OKAY) &&
+ params[i].type == VIR_TYPED_PARAM_STRING)) {
--*ret_params_len;
continue;
}
@@ -3534,10 +3537,7 @@ remoteDispatchDomainGetCPUStats(virNetServerPtr
server ATTRIBUTE_UNUSED,
virDomainPtr dom = NULL;
struct daemonClientPrivate *priv;
virTypedParameterPtr params = NULL;
- remote_typed_param *info = NULL;
- int cpu, idx, src, dest;
int rv = -1;
- unsigned int return_size = 0;
int percpu_len = 0;
priv = virNetServerClientGetPrivateData(client);
@@ -3546,128 +3546,53 @@ remoteDispatchDomainGetCPUStats(virNetServerPtr
server ATTRIBUTE_UNUSED,
goto cleanup;
}
- /*
- * nparams * ncpus should be smaller than
- * REMOTE_DOMAIN_GET_CPU_STATS_MAX but nparams
- * and ncpus are limited by API. check it.
- */
- if (args->nparams > 16) {
+ if (args->nparams > REMOTE_NODE_CPU_STATS_MAX) {
virNetError(VIR_ERR_INTERNAL_ERROR, "%s", _("nparams too
large"));
goto cleanup;
}
- if (args->ncpus > 128) {
+ if (args->ncpus > REMOTE_DOMAIN_GET_CPU_STATS_NCPUS_MAX) {
virNetError(VIR_ERR_INTERNAL_ERROR, "%s", _("ncpus too
large"));
goto cleanup;
}
- if (args->nparams > 0) {
- if (VIR_ALLOC_N(params, args->ncpus * args->nparams) < 0)
- goto no_memory;
+ if (args->nparams > 0 &&
+ VIR_ALLOC_N(params, args->ncpus * args->nparams) < 0) {
+ virReportOOMError();
+ goto cleanup;
}
if (!(dom = get_nonnull_domain(priv->conn, args->dom)))
goto cleanup;
percpu_len = virDomainGetCPUStats(dom, params, args->nparams,
- args->start_cpu, args->ncpus, args->flags);
+ args->start_cpu, args->ncpus,
+ args->flags);
if (percpu_len < 0)
goto cleanup;
- /* If nparams == 0, the function returns a sigle value */
+ /* If nparams == 0, the function returns a single value */
if (args->nparams == 0)
goto success;
- /*
- * Here, we return values based on the real param size returned by
- * a driver rather than the passed one. RPC Client stub should decode
- * it to fit callar's expectation.
- *
- * If cpu ID is sparse, The whole array for some cpu IDs will be
- * zero-cleared. This doesn't meet to remoteSerializeTypedParameters()
- * and we do serialization by ourselves.
- */
- return_size = percpu_len * args->ncpus;
- if (VIR_ALLOC_N(info, return_size) < 0)
- goto no_memory;
+ if (remoteSerializeTypedParameters(params, args->nparams * args->ncpus,
+ &ret->params.params_val,
+ &ret->params.params_len,
+ args->flags) < 0)
+ goto cleanup;
+
+ percpu_len = ret->params.params_len / args->ncpus;
- for (cpu = 0; cpu < args->ncpus; ++cpu) {
- for (idx = 0; idx < percpu_len; ++idx) {
- src = cpu * args->nparams + idx;
- dest = cpu * percpu_len + idx;
-
- /* If CPU ID is discontiguous, this can happen */
- if (params[src].type == 0)
- continue;
-
- info[dest].field = strdup(params[src].field);
- if (info[dest].field == NULL)
- goto no_memory;
-
- info[dest].value.type = params[src].type;
-
- switch (params[src].type) {
- case VIR_TYPED_PARAM_INT:
- info[dest].value.remote_typed_param_value_u.i =
- params[src].value.i;
- break;
- case VIR_TYPED_PARAM_UINT:
- info[dest].value.remote_typed_param_value_u.ui =
- params[src].value.ui;
- break;
- case VIR_TYPED_PARAM_LLONG:
- info[dest].value.remote_typed_param_value_u.l =
- params[src].value.l;
- break;
- case VIR_TYPED_PARAM_ULLONG:
- info[dest].value.remote_typed_param_value_u.ul =
- params[src].value.ul;
- break;
- case VIR_TYPED_PARAM_DOUBLE:
- info[dest].value.remote_typed_param_value_u.d =
- params[src].value.d;
- break;
- case VIR_TYPED_PARAM_BOOLEAN:
- info[dest].value.remote_typed_param_value_u.b =
- params[src].value.b;
- break;
- case VIR_TYPED_PARAM_STRING:
- info[dest].value.remote_typed_param_value_u.s =
- strdup(params[src].value.s);
- if (info[dest].value.remote_typed_param_value_u.s == NULL)
- goto no_memory;
- break;
- default:
- virNetError(VIR_ERR_RPC, _("unknown parameter type: %d"),
- params[src].type);
- goto cleanup;
- }
- }
- }
success:
rv = 0;
- ret->params.params_len = return_size;
- ret->params.params_val = info;
ret->nparams = percpu_len;
+
cleanup:
- if (rv < 0) {
+ if (rv < 0)
virNetMessageSaveError(rerr);
- if (info) {
- int i;
- for (i = 0; i < return_size; i++) {
- VIR_FREE(info[i].field);
- if (info[i].value.type == VIR_TYPED_PARAM_STRING)
- VIR_FREE(info[i].value.remote_typed_param_value_u.s);
- }
- VIR_FREE(info);
- }
- }
virTypedParameterArrayClear(params, args->ncpus * args->nparams);
VIR_FREE(params);
if (dom)
virDomainFree(dom);
return rv;
-no_memory:
- virReportOOMError();
- goto cleanup;
}
/*----- Helpers. -----*/
diff --git i/src/libvirt.c w/src/libvirt.c
index 7060f5a..e702a34 100644
--- i/src/libvirt.c
+++ w/src/libvirt.c
@@ -18162,7 +18162,7 @@ error:
* @nparams: number of parameters per cpu
* @start_cpu: which cpu to start with, or -1 for summary
* @ncpus: how many cpus to query
- * @flags: extra flags; not used yet, so callers should always pass 0
+ * @flags: bitwise-OR of virTypedParameterFlags
*
* Get statistics relating to CPU usage attributable to a single
* domain (in contrast to the statistics returned by
@@ -18251,20 +18251,19 @@ int virDomainGetCPUStats(virDomainPtr domain,
* if start_cpu is -1, ncpus must be 1
* params == NULL must match nparams == 0
* ncpus must be non-zero unless params == NULL
+ * nparams * ncpus must not overflow (RPC may restrict it even more)
*/
if (start_cpu < -1 ||
(start_cpu == -1 && ncpus != 1) ||
((params == NULL) != (nparams == 0)) ||
- (ncpus == 0 && params != NULL)) {
- virLibDomainError(VIR_ERR_INVALID_ARG, __FUNCTION__);
- goto error;
- }
-
- /* remote protocol doesn't welcome big args in one shot */
- if ((nparams > 16) || (ncpus > 128)) {
+ (ncpus == 0 && params != NULL) ||
+ ncpus < UINT_MAX / nparams) {
virLibDomainError(VIR_ERR_INVALID_ARG, __FUNCTION__);
goto error;
}
+ if (VIR_DRV_SUPPORTS_FEATURE(domain->conn->driver, domain->conn,
+ VIR_DRV_FEATURE_TYPED_PARAM_STRING))
+ flags |= VIR_TYPED_PARAM_STRING_OKAY;
if (conn->driver->domainGetCPUStats) {
int ret;
diff --git i/src/remote/remote_driver.c w/src/remote/remote_driver.c
index a3ca63e..2312cf9 100644
--- i/src/remote/remote_driver.c
+++ w/src/remote/remote_driver.c
@@ -2,7 +2,7 @@
* remote_driver.c: driver to provide access to libvirtd running
* on a remote machine
*
- * Copyright (C) 2007-2011 Red Hat, Inc.
+ * Copyright (C) 2007-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
@@ -2315,12 +2315,24 @@ static int remoteDomainGetCPUStats(virDomainPtr
domain,
struct private_data *priv = domain->conn->privateData;
remote_domain_get_cpu_stats_args args;
remote_domain_get_cpu_stats_ret ret;
- remote_typed_param *info;
int rv = -1;
- int cpu, idx, src, dest;
+ int cpu;
remoteDriverLock(priv);
+ if (nparams > REMOTE_NODE_CPU_STATS_MAX) {
+ remoteError(VIR_ERR_RPC,
+ _("nparams count exceeds maximum: %u > %u"),
+ nparams, REMOTE_NODE_CPU_STATS_MAX);
+ goto done;
+ }
+ if (ncpus > REMOTE_DOMAIN_GET_CPU_STATS_NCPUS_MAX) {
+ remoteError(VIR_ERR_RPC,
+ _("ncpus count exceeds maximum: %u > %u"),
+ ncpus, REMOTE_DOMAIN_GET_CPU_STATS_NCPUS_MAX);
+ goto done;
+ }
+
make_nonnull_domain(&args.dom, domain);
args.nparams = nparams;
args.start_cpu = start_cpu;
@@ -2336,67 +2348,38 @@ static int remoteDomainGetCPUStats(virDomainPtr
domain,
(char *) &ret) == -1)
goto done;
+ /* Check the length of the returned list carefully. */
+ if (ret.params.params_len > nparams * ncpus ||
+ (ret.params.params_len &&
+ ret.nparams * ncpus != ret.params.params_len)) {
+ remoteError(VIR_ERR_RPC, "%s",
+ _("remoteDomainGetCPUStats: "
+ "returned number of stats exceeds limit"));
+ memset(params, 0, sizeof(*params) * nparams * ncpus);
+ goto cleanup;
+ }
+
+ /* Handle the case when the caller does not know the number of stats
+ * and is asking for the number of stats supported
+ */
if (nparams == 0) {
rv = ret.nparams;
goto cleanup;
}
- /*
- * the returned arrray's size is not same to nparams * ncpus. And
- * if cpu ID is not contiguous, all-zero entries can be found.
+
+ /* The remote side did not send back any zero entries, so we have
+ * to expand things back into a possibly sparse array.
*/
- memset(params, 0, sizeof(virTypedParameter) * nparams * ncpus);
-
- /* Here, ret.nparams is always smaller than nparams */
- info = ret.params.params_val;
-
- for (cpu = 0; cpu < ncpus; ++cpu) {
- for (idx = 0; idx < ret.nparams; ++idx) {
- src = cpu * ret.nparams + idx;
- dest = cpu * nparams + idx;
-
- if (info[src].value.type == 0) /* skip zeroed ones */
- continue;
-
- params[dest].type = info[src].value.type;
- strcpy(params[dest].field, info[src].field);
-
- switch (params[dest].type) {
- case VIR_TYPED_PARAM_INT:
- params[dest].value.i =
- info[src].value.remote_typed_param_value_u.i;
- break;
- case VIR_TYPED_PARAM_UINT:
- params[dest].value.ui =
- info[src].value.remote_typed_param_value_u.ui;
- break;
- case VIR_TYPED_PARAM_LLONG:
- params[dest].value.l =
- info[src].value.remote_typed_param_value_u.l;
- break;
- case VIR_TYPED_PARAM_ULLONG:
- params[dest].value.ul =
- info[src].value.remote_typed_param_value_u.ul;
- break;
- case VIR_TYPED_PARAM_DOUBLE:
- params[dest].value.d =
- info[src].value.remote_typed_param_value_u.d;
- break;
- case VIR_TYPED_PARAM_BOOLEAN:
- params[dest].value.b =
- info[src].value.remote_typed_param_value_u.b;
- break;
- case VIR_TYPED_PARAM_STRING:
- params[dest].value.s =
- strdup(info[src].value.remote_typed_param_value_u.s);
- if (params[dest].value.s == NULL)
- goto out_of_memory;
- break;
- default:
- remoteError(VIR_ERR_RPC, _("unknown parameter type: %d"),
- params[dest].type);
- goto cleanup;
- }
- }
+ memset(params, 0, sizeof(*params) * nparams * ncpus);
+ for (cpu = 0; cpu < ncpus; cpu++) {
+ int tmp = nparams;
+ remote_typed_param *stride = &ret.params.params_val[cpu *
ret.nparams];
+
+ if (remoteDeserializeTypedParameters(stride, ret.nparams,
+ REMOTE_NODE_CPU_STATS_MAX,
+ ¶ms[cpu * nparams],
+ &tmp) < 0)
+ goto cleanup;
}
rv = ret.nparams;
@@ -2415,9 +2398,6 @@ cleanup:
done:
remoteDriverUnlock(priv);
return rv;
-out_of_memory:
- virReportOOMError();
- goto cleanup;
}
diff --git i/src/remote/remote_protocol.x w/src/remote/remote_protocol.x
index 0bd399c..b58925a 100644
--- i/src/remote/remote_protocol.x
+++ w/src/remote/remote_protocol.x
@@ -209,8 +209,13 @@ const REMOTE_DOMAIN_SEND_KEY_MAX = 16;
const REMOTE_DOMAIN_INTERFACE_PARAMETERS_MAX = 16;
/*
- * Upper limit on list of (real) cpu parameters
- * nparams(<=16) * ncpus(<=128) <= 2048.
+ * Upper limit on cpus involved in per-cpu stats
+ */
+const REMOTE_DOMAIN_GET_CPU_STATS_NCPUS_MAX = 128;
+
+/*
+ * Upper limit on list of per-cpu stats:
+ * REMOTE_NODE_CPU_STATS_MAX * REMOTE_DOMAIN_GET_CPU_STATS_MAX
*/
const REMOTE_DOMAIN_GET_CPU_STATS_MAX = 2048;
--
Eric Blake eblake(a)redhat.com +1-919-301-3266
Libvirt virtualization library
http://libvirt.org