Re: [libvirt] [PATCH v3 3/5] hyperv: add hypervInvokeMethod

2017-05-16 Thread Matthias Bolte
2017-05-05 22:24 GMT+02:00  :
> On Tue, 2017-05-02 at 00:26 +0200, Matthias Bolte wrote:
>> 2017-04-24 20:19 GMT+02:00 Sri Ramanujam :
>> > This commit adds support for invoking methods on remote objects
>> > via hypervInvokeMethod.
>> > ---
>> >  src/hyperv/hyperv_wmi.c | 569
>> > 
>> >  src/hyperv/hyperv_wmi.h |   3 +
>> >  src/hyperv/openwsman.h  |   4 +
>> >  3 files changed, 576 insertions(+)
>> >
>> > diff --git a/src/hyperv/hyperv_wmi.c b/src/hyperv/hyperv_wmi.c
>> > index 1960c4c..deea907 100644
>> > --- a/src/hyperv/hyperv_wmi.c
>> > +++ b/src/hyperv/hyperv_wmi.c
>> > @@ -24,6 +24,7 @@
>> >   */
>> >
>> >  #include 
>> > +#include 
>> >
>> >  #include "internal.h"
>> >  #include "virerror.h"
>> > @@ -34,11 +35,14 @@
>> >  #include "hyperv_private.h"
>> >  #include "hyperv_wmi.h"
>> >  #include "virstring.h"
>> > +#include "openwsman.h"
>> > +#include "virlog.h"
>> >
>> >  #define WS_SERIALIZER_FREE_MEM_WORKS 0
>> >
>> >  #define VIR_FROM_THIS VIR_FROM_HYPERV
>> >
>> > +VIR_LOG_INIT("hyperv.hyperv_wmi");
>> >
>> >  static int
>> >  hypervGetWmiClassInfo(hypervPrivate *priv,
>> > hypervWmiClassInfoListPtr list,
>> > @@ -406,6 +410,571 @@
>> > hypervAddEmbeddedParam(hypervInvokeParamsListPtr params,
>> > hypervPrivate *priv,
>> >  }
>> >
>> >
>> > +/*
>> > + * Serializing parameters to XML and invoking methods
>> > + */
>> > +
>> > +static int
>> > +hypervGetCimTypeInfo(hypervCimTypePtr typemap, const char *name,
>> > +hypervCimTypePtr *property)
>> > +{
>> > +size_t i = 0;
>> > +while (typemap[i].name[0] != '\0') {
>> > +if (STREQ(typemap[i].name, name)) {
>> > +*property = [i];
>> > +return 0;
>> > +}
>> > +i++;
>> > +}
>> > +
>> > +return -1;
>> > +}
>> > +
>> > +
>> > +static int
>> > +hypervCreateInvokeXmlDoc(hypervInvokeParamsListPtr params,
>> > WsXmlDocH *docRoot)
>> > +{
>> > +int result = -1;
>> > +char *method = NULL;
>> > +WsXmlNodeH xmlNodeMethod = NULL;
>> > +
>> > +if (virAsprintf(, "%s_INPUT", params->method) < 0)
>> > +goto error;
>> > +
>> > +*docRoot = ws_xml_create_doc(NULL, method);
>> > +if (*docRoot == NULL) {
>> > +virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
>> > +_("Could not instantiate XML document"));
>> > +goto error;
>> > +}
>> > +
>> > +xmlNodeMethod = xml_parser_get_root(*docRoot);
>> > +if (xmlNodeMethod == NULL) {
>> > +virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
>> > +_("Could not get root node of XML document"));
>> > +goto error;
>> > +}
>> > +
>> > +/* add resource URI as namespace */
>> > +ws_xml_set_ns(xmlNodeMethod, params->resourceUri, "p");
>> > +
>> > +result = 0;
>> > +goto cleanup;
>> > +
>> > + error:
>> > +if (*docRoot != NULL) {
>> > +ws_xml_destroy_doc(*docRoot);
>> > +*docRoot = NULL;
>> > +}
>> > + cleanup:
>> > +VIR_FREE(method);
>> > +return result;
>>
>> The error and cleanup label could be merged:
>>
>>
>> result = 0;
>>
>> cleanup:
>> if (result < 0 && *docRoot != NULL) {
>> ws_xml_destroy_doc(*docRoot);
>> *docRoot = NULL;
>> }
>>
>> VIR_FREE(method);
>> return result;
>> }
>>
>>
>> > +}
>> > +
>> > +static int
>> > +hypervSerializeSimpleParam(hypervParamPtr p, const char
>> > *resourceUri,
>> > +WsXmlNodeH *methodNode)
>> > +{
>> > +int result = -1;
>> > +WsXmlNodeH xmlNodeParam = NULL;
>> > +
>> > +xmlNodeParam = ws_xml_add_child(*methodNode, resourceUri,
>> > +p->simple.name, p->simple.value);
>> > +if (xmlNodeParam == NULL) {
>> > +virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
>> > +_("Could not create simple param"));
>> > +goto cleanup;
>>
>> There is no actual cleanup, just return -1 here.
>>
>> > +}
>> > +
>> > +result = 0;
>> > +
>> > + cleanup:
>> > +return result;
>>
>> And just return 0 here.
>>
>> > +}
>> > +
>> > +static int
>> > +hypervSerializeEprParam(hypervParamPtr p, hypervPrivate *priv,
>> > +const char *resourceUri, WsXmlDocH doc, WsXmlNodeH
>> > *methodNode)
>> > +{
>> > +int result = -1;
>> > +WsXmlNodeH xmlNodeParam = NULL,
>> > +   xmlNodeTemp = NULL,
>> > +   xmlNodeAddr = NULL,
>> > +   xmlNodeRef = NULL;
>> > +xmlNodePtr xmlNodeAddrPtr = NULL,
>> > +   xmlNodeRefPtr = NULL;
>> > +WsXmlDocH xmlDocResponse = NULL;
>> > +xmlDocPtr docPtr = (xmlDocPtr) doc->parserDoc;
>> > +WsXmlNsH ns = NULL;
>> > +client_opt_t *options = NULL;
>> > +filter_t *filter = NULL;
>> > +char *enumContext = NULL;
>> > +char *query_string = NULL;
>> > +
>> > +/* init and set up options */
>> > +options = wsmc_options_init();
>> > +if (!options) {
>> > +virReportError(VIR_ERR_INTERNAL_ERROR, "%s", 

Re: [libvirt] [PATCH v3 3/5] hyperv: add hypervInvokeMethod

2017-05-05 Thread sramanujam
On Tue, 2017-05-02 at 00:26 +0200, Matthias Bolte wrote:
> 2017-04-24 20:19 GMT+02:00 Sri Ramanujam :
> > This commit adds support for invoking methods on remote objects
> > via hypervInvokeMethod.
> > ---
> >  src/hyperv/hyperv_wmi.c | 569
> > 
> >  src/hyperv/hyperv_wmi.h |   3 +
> >  src/hyperv/openwsman.h  |   4 +
> >  3 files changed, 576 insertions(+)
> > 
> > diff --git a/src/hyperv/hyperv_wmi.c b/src/hyperv/hyperv_wmi.c
> > index 1960c4c..deea907 100644
> > --- a/src/hyperv/hyperv_wmi.c
> > +++ b/src/hyperv/hyperv_wmi.c
> > @@ -24,6 +24,7 @@
> >   */
> > 
> >  #include 
> > +#include 
> > 
> >  #include "internal.h"
> >  #include "virerror.h"
> > @@ -34,11 +35,14 @@
> >  #include "hyperv_private.h"
> >  #include "hyperv_wmi.h"
> >  #include "virstring.h"
> > +#include "openwsman.h"
> > +#include "virlog.h"
> > 
> >  #define WS_SERIALIZER_FREE_MEM_WORKS 0
> > 
> >  #define VIR_FROM_THIS VIR_FROM_HYPERV
> > 
> > +VIR_LOG_INIT("hyperv.hyperv_wmi");
> > 
> >  static int
> >  hypervGetWmiClassInfo(hypervPrivate *priv,
> > hypervWmiClassInfoListPtr list,
> > @@ -406,6 +410,571 @@
> > hypervAddEmbeddedParam(hypervInvokeParamsListPtr params,
> > hypervPrivate *priv,
> >  }
> > 
> > 
> > +/*
> > + * Serializing parameters to XML and invoking methods
> > + */
> > +
> > +static int
> > +hypervGetCimTypeInfo(hypervCimTypePtr typemap, const char *name,
> > +hypervCimTypePtr *property)
> > +{
> > +size_t i = 0;
> > +while (typemap[i].name[0] != '\0') {
> > +if (STREQ(typemap[i].name, name)) {
> > +*property = [i];
> > +return 0;
> > +}
> > +i++;
> > +}
> > +
> > +return -1;
> > +}
> > +
> > +
> > +static int
> > +hypervCreateInvokeXmlDoc(hypervInvokeParamsListPtr params,
> > WsXmlDocH *docRoot)
> > +{
> > +int result = -1;
> > +char *method = NULL;
> > +WsXmlNodeH xmlNodeMethod = NULL;
> > +
> > +if (virAsprintf(, "%s_INPUT", params->method) < 0)
> > +goto error;
> > +
> > +*docRoot = ws_xml_create_doc(NULL, method);
> > +if (*docRoot == NULL) {
> > +virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> > +_("Could not instantiate XML document"));
> > +goto error;
> > +}
> > +
> > +xmlNodeMethod = xml_parser_get_root(*docRoot);
> > +if (xmlNodeMethod == NULL) {
> > +virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> > +_("Could not get root node of XML document"));
> > +goto error;
> > +}
> > +
> > +/* add resource URI as namespace */
> > +ws_xml_set_ns(xmlNodeMethod, params->resourceUri, "p");
> > +
> > +result = 0;
> > +goto cleanup;
> > +
> > + error:
> > +if (*docRoot != NULL) {
> > +ws_xml_destroy_doc(*docRoot);
> > +*docRoot = NULL;
> > +}
> > + cleanup:
> > +VIR_FREE(method);
> > +return result;
> 
> The error and cleanup label could be merged:
> 
> 
> result = 0;
> 
> cleanup:
> if (result < 0 && *docRoot != NULL) {
> ws_xml_destroy_doc(*docRoot);
> *docRoot = NULL;
> }
> 
> VIR_FREE(method);
> return result;
> }
> 
> 
> > +}
> > +
> > +static int
> > +hypervSerializeSimpleParam(hypervParamPtr p, const char
> > *resourceUri,
> > +WsXmlNodeH *methodNode)
> > +{
> > +int result = -1;
> > +WsXmlNodeH xmlNodeParam = NULL;
> > +
> > +xmlNodeParam = ws_xml_add_child(*methodNode, resourceUri,
> > +p->simple.name, p->simple.value);
> > +if (xmlNodeParam == NULL) {
> > +virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> > +_("Could not create simple param"));
> > +goto cleanup;
> 
> There is no actual cleanup, just return -1 here.
> 
> > +}
> > +
> > +result = 0;
> > +
> > + cleanup:
> > +return result;
> 
> And just return 0 here.
> 
> > +}
> > +
> > +static int
> > +hypervSerializeEprParam(hypervParamPtr p, hypervPrivate *priv,
> > +const char *resourceUri, WsXmlDocH doc, WsXmlNodeH
> > *methodNode)
> > +{
> > +int result = -1;
> > +WsXmlNodeH xmlNodeParam = NULL,
> > +   xmlNodeTemp = NULL,
> > +   xmlNodeAddr = NULL,
> > +   xmlNodeRef = NULL;
> > +xmlNodePtr xmlNodeAddrPtr = NULL,
> > +   xmlNodeRefPtr = NULL;
> > +WsXmlDocH xmlDocResponse = NULL;
> > +xmlDocPtr docPtr = (xmlDocPtr) doc->parserDoc;
> > +WsXmlNsH ns = NULL;
> > +client_opt_t *options = NULL;
> > +filter_t *filter = NULL;
> > +char *enumContext = NULL;
> > +char *query_string = NULL;
> > +
> > +/* init and set up options */
> > +options = wsmc_options_init();
> > +if (!options) {
> > +virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not
> > init options"));
> > +goto cleanup;
> > +}
> > +wsmc_set_action_option(options, FLAG_ENUMERATION_ENUM_EPR);
> > +
> > +/* Get query and create filter based on it */
> > +  

Re: [libvirt] [PATCH v3 3/5] hyperv: add hypervInvokeMethod

2017-05-01 Thread Matthias Bolte
2017-04-24 20:19 GMT+02:00 Sri Ramanujam :
> This commit adds support for invoking methods on remote objects
> via hypervInvokeMethod.
> ---
>  src/hyperv/hyperv_wmi.c | 569 
> 
>  src/hyperv/hyperv_wmi.h |   3 +
>  src/hyperv/openwsman.h  |   4 +
>  3 files changed, 576 insertions(+)
>
> diff --git a/src/hyperv/hyperv_wmi.c b/src/hyperv/hyperv_wmi.c
> index 1960c4c..deea907 100644
> --- a/src/hyperv/hyperv_wmi.c
> +++ b/src/hyperv/hyperv_wmi.c
> @@ -24,6 +24,7 @@
>   */
>
>  #include 
> +#include 
>
>  #include "internal.h"
>  #include "virerror.h"
> @@ -34,11 +35,14 @@
>  #include "hyperv_private.h"
>  #include "hyperv_wmi.h"
>  #include "virstring.h"
> +#include "openwsman.h"
> +#include "virlog.h"
>
>  #define WS_SERIALIZER_FREE_MEM_WORKS 0
>
>  #define VIR_FROM_THIS VIR_FROM_HYPERV
>
> +VIR_LOG_INIT("hyperv.hyperv_wmi");
>
>  static int
>  hypervGetWmiClassInfo(hypervPrivate *priv, hypervWmiClassInfoListPtr list,
> @@ -406,6 +410,571 @@ hypervAddEmbeddedParam(hypervInvokeParamsListPtr 
> params, hypervPrivate *priv,
>  }
>
>
> +/*
> + * Serializing parameters to XML and invoking methods
> + */
> +
> +static int
> +hypervGetCimTypeInfo(hypervCimTypePtr typemap, const char *name,
> +hypervCimTypePtr *property)
> +{
> +size_t i = 0;
> +while (typemap[i].name[0] != '\0') {
> +if (STREQ(typemap[i].name, name)) {
> +*property = [i];
> +return 0;
> +}
> +i++;
> +}
> +
> +return -1;
> +}
> +
> +
> +static int
> +hypervCreateInvokeXmlDoc(hypervInvokeParamsListPtr params, WsXmlDocH 
> *docRoot)
> +{
> +int result = -1;
> +char *method = NULL;
> +WsXmlNodeH xmlNodeMethod = NULL;
> +
> +if (virAsprintf(, "%s_INPUT", params->method) < 0)
> +goto error;
> +
> +*docRoot = ws_xml_create_doc(NULL, method);
> +if (*docRoot == NULL) {
> +virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> +_("Could not instantiate XML document"));
> +goto error;
> +}
> +
> +xmlNodeMethod = xml_parser_get_root(*docRoot);
> +if (xmlNodeMethod == NULL) {
> +virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> +_("Could not get root node of XML document"));
> +goto error;
> +}
> +
> +/* add resource URI as namespace */
> +ws_xml_set_ns(xmlNodeMethod, params->resourceUri, "p");
> +
> +result = 0;
> +goto cleanup;
> +
> + error:
> +if (*docRoot != NULL) {
> +ws_xml_destroy_doc(*docRoot);
> +*docRoot = NULL;
> +}
> + cleanup:
> +VIR_FREE(method);
> +return result;

The error and cleanup label could be merged:


result = 0;

cleanup:
if (result < 0 && *docRoot != NULL) {
ws_xml_destroy_doc(*docRoot);
*docRoot = NULL;
}

VIR_FREE(method);
return result;
}


> +}
> +
> +static int
> +hypervSerializeSimpleParam(hypervParamPtr p, const char *resourceUri,
> +WsXmlNodeH *methodNode)
> +{
> +int result = -1;
> +WsXmlNodeH xmlNodeParam = NULL;
> +
> +xmlNodeParam = ws_xml_add_child(*methodNode, resourceUri,
> +p->simple.name, p->simple.value);
> +if (xmlNodeParam == NULL) {
> +virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> +_("Could not create simple param"));
> +goto cleanup;

There is no actual cleanup, just return -1 here.

> +}
> +
> +result = 0;
> +
> + cleanup:
> +return result;

And just return 0 here.

> +}
> +
> +static int
> +hypervSerializeEprParam(hypervParamPtr p, hypervPrivate *priv,
> +const char *resourceUri, WsXmlDocH doc, WsXmlNodeH *methodNode)
> +{
> +int result = -1;
> +WsXmlNodeH xmlNodeParam = NULL,
> +   xmlNodeTemp = NULL,
> +   xmlNodeAddr = NULL,
> +   xmlNodeRef = NULL;
> +xmlNodePtr xmlNodeAddrPtr = NULL,
> +   xmlNodeRefPtr = NULL;
> +WsXmlDocH xmlDocResponse = NULL;
> +xmlDocPtr docPtr = (xmlDocPtr) doc->parserDoc;
> +WsXmlNsH ns = NULL;
> +client_opt_t *options = NULL;
> +filter_t *filter = NULL;
> +char *enumContext = NULL;
> +char *query_string = NULL;
> +
> +/* init and set up options */
> +options = wsmc_options_init();
> +if (!options) {
> +virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not init 
> options"));
> +goto cleanup;
> +}
> +wsmc_set_action_option(options, FLAG_ENUMERATION_ENUM_EPR);
> +
> +/* Get query and create filter based on it */
> +query_string = virBufferContentAndReset(p->epr.query);
> +filter = filter_create_simple(WSM_WQL_FILTER_DIALECT, query_string);
> +if (!filter) {
> +virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not create WQL 
> filter"));
> +goto cleanup;
> +}
> +
> +/* enumerate based on the filter from this query */
> +xmlDocResponse = wsmc_action_enumerate(priv->client, 
> p->epr.info->rootUri,
> 

[libvirt] [PATCH v3 3/5] hyperv: add hypervInvokeMethod

2017-04-24 Thread Sri Ramanujam
This commit adds support for invoking methods on remote objects
via hypervInvokeMethod.
---
 src/hyperv/hyperv_wmi.c | 569 
 src/hyperv/hyperv_wmi.h |   3 +
 src/hyperv/openwsman.h  |   4 +
 3 files changed, 576 insertions(+)

diff --git a/src/hyperv/hyperv_wmi.c b/src/hyperv/hyperv_wmi.c
index 1960c4c..deea907 100644
--- a/src/hyperv/hyperv_wmi.c
+++ b/src/hyperv/hyperv_wmi.c
@@ -24,6 +24,7 @@
  */
 
 #include 
+#include 
 
 #include "internal.h"
 #include "virerror.h"
@@ -34,11 +35,14 @@
 #include "hyperv_private.h"
 #include "hyperv_wmi.h"
 #include "virstring.h"
+#include "openwsman.h"
+#include "virlog.h"
 
 #define WS_SERIALIZER_FREE_MEM_WORKS 0
 
 #define VIR_FROM_THIS VIR_FROM_HYPERV
 
+VIR_LOG_INIT("hyperv.hyperv_wmi");
 
 static int
 hypervGetWmiClassInfo(hypervPrivate *priv, hypervWmiClassInfoListPtr list,
@@ -406,6 +410,571 @@ hypervAddEmbeddedParam(hypervInvokeParamsListPtr params, 
hypervPrivate *priv,
 }
 
 
+/*
+ * Serializing parameters to XML and invoking methods
+ */
+
+static int
+hypervGetCimTypeInfo(hypervCimTypePtr typemap, const char *name,
+hypervCimTypePtr *property)
+{
+size_t i = 0;
+while (typemap[i].name[0] != '\0') {
+if (STREQ(typemap[i].name, name)) {
+*property = [i];
+return 0;
+}
+i++;
+}
+
+return -1;
+}
+
+
+static int
+hypervCreateInvokeXmlDoc(hypervInvokeParamsListPtr params, WsXmlDocH *docRoot)
+{
+int result = -1;
+char *method = NULL;
+WsXmlNodeH xmlNodeMethod = NULL;
+
+if (virAsprintf(, "%s_INPUT", params->method) < 0)
+goto error;
+
+*docRoot = ws_xml_create_doc(NULL, method);
+if (*docRoot == NULL) {
+virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+_("Could not instantiate XML document"));
+goto error;
+}
+
+xmlNodeMethod = xml_parser_get_root(*docRoot);
+if (xmlNodeMethod == NULL) {
+virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+_("Could not get root node of XML document"));
+goto error;
+}
+
+/* add resource URI as namespace */
+ws_xml_set_ns(xmlNodeMethod, params->resourceUri, "p");
+
+result = 0;
+goto cleanup;
+
+ error:
+if (*docRoot != NULL) {
+ws_xml_destroy_doc(*docRoot);
+*docRoot = NULL;
+}
+ cleanup:
+VIR_FREE(method);
+return result;
+}
+
+static int
+hypervSerializeSimpleParam(hypervParamPtr p, const char *resourceUri,
+WsXmlNodeH *methodNode)
+{
+int result = -1;
+WsXmlNodeH xmlNodeParam = NULL;
+
+xmlNodeParam = ws_xml_add_child(*methodNode, resourceUri,
+p->simple.name, p->simple.value);
+if (xmlNodeParam == NULL) {
+virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+_("Could not create simple param"));
+goto cleanup;
+}
+
+result = 0;
+
+ cleanup:
+return result;
+}
+
+static int
+hypervSerializeEprParam(hypervParamPtr p, hypervPrivate *priv,
+const char *resourceUri, WsXmlDocH doc, WsXmlNodeH *methodNode)
+{
+int result = -1;
+WsXmlNodeH xmlNodeParam = NULL,
+   xmlNodeTemp = NULL,
+   xmlNodeAddr = NULL,
+   xmlNodeRef = NULL;
+xmlNodePtr xmlNodeAddrPtr = NULL,
+   xmlNodeRefPtr = NULL;
+WsXmlDocH xmlDocResponse = NULL;
+xmlDocPtr docPtr = (xmlDocPtr) doc->parserDoc;
+WsXmlNsH ns = NULL;
+client_opt_t *options = NULL;
+filter_t *filter = NULL;
+char *enumContext = NULL;
+char *query_string = NULL;
+
+/* init and set up options */
+options = wsmc_options_init();
+if (!options) {
+virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not init 
options"));
+goto cleanup;
+}
+wsmc_set_action_option(options, FLAG_ENUMERATION_ENUM_EPR);
+
+/* Get query and create filter based on it */
+query_string = virBufferContentAndReset(p->epr.query);
+filter = filter_create_simple(WSM_WQL_FILTER_DIALECT, query_string);
+if (!filter) {
+virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not create WQL 
filter"));
+goto cleanup;
+}
+
+/* enumerate based on the filter from this query */
+xmlDocResponse = wsmc_action_enumerate(priv->client, p->epr.info->rootUri,
+options, filter);
+if (hypervVerifyResponse(priv->client, xmlDocResponse, "enumeration") < 0)
+goto cleanup;
+
+/* Get context */
+enumContext = wsmc_get_enum_context(xmlDocResponse);
+ws_xml_destroy_doc(xmlDocResponse);
+
+/* Pull using filter and enum context */
+xmlDocResponse = wsmc_action_pull(priv->client, resourceUri, options,
+filter, enumContext);
+
+if (hypervVerifyResponse(priv->client, xmlDocResponse, "pull") < 0)
+goto cleanup;
+
+/* drill down and extract EPR node children */
+if (!(xmlNodeTemp = ws_xml_get_soap_body(xmlDocResponse))) {
+virReportError(VIR_ERR_INTERNAL_ERROR, "%s",