Refreshed dbg_pv_dump patch. Added needed upstream patch.

ngcp3.0
Victor Seva 13 years ago
parent 7c7babc550
commit 371a0af8a2

@ -7,6 +7,7 @@ upstream/modules-usrloc-added-missing-rpc-commands.patch
upstream/core-xavp-Added-helper-function-to-get-a-list-of-key.patch
upstream/app_lua-Added-sr.xavp.get_keys-function.patch
upstream/app_lua-Added-sr.xavp.get-function-in-order-to-get-a.patch
upstream/0001-core-do-not-repeat-key-names-on-xavp_get_list_key_na.patch
upstream/debug/0002-core-updated-dprint-api-to-enable-support-for-debug-.patch
upstream/debug/0003-debugger-option-to-set-debug-level-per-module.patch
upstream/debug/core-use-pointer-to-pv_spec_t-on-lvalue-union.-Use-p.patch

@ -0,0 +1,73 @@
From 519fe88fc41cae4674ce4bc943b84eb522af654b Mon Sep 17 00:00:00 2001
From: Victor Seva <linuxmaniac@torreviejawireless.org>
Date: Wed, 12 Jun 2013 15:58:29 +0200
Subject: [PATCH] core: do not repeat key names on xavp_get_list_key_names
function result
---
xavp.c | 32 +++++++++++++++++++++++++-------
1 file changed, 25 insertions(+), 7 deletions(-)
diff --git a/xavp.c b/xavp.c
index adce5d1..ceb405d 100644
--- a/xavp.c
+++ b/xavp.c
@@ -546,15 +546,20 @@ void xavp_print_list(sr_xavp_t **head)
* $xavp(test[0]=>two) = "2"
* $xavp(test[0]=>three) = 3
* $xavp(test[0]=>four) = $xavp(whatever)
+ * $xavp(test[0]=>two) = "other 2"
*
* xavp_get_list_keys_names(test[0]) returns
* {"one", "two", "three", "four"}
+ *
+ * free the struct str_list afterwards
+ * but do *NO* free the strings inside
*/
struct str_list *xavp_get_list_key_names(sr_xavp_t *xavp)
{
sr_xavp_t *avp = NULL;
struct str_list *result = NULL;
struct str_list *r = NULL;
+ struct str_list *f = NULL;
int total = 0;
if(xavp==NULL){
@@ -585,14 +590,27 @@ struct str_list *xavp_get_list_key_names(sr_xavp_t *xavp)
while(avp)
{
- r = append_str_list(avp->name.s, avp->name.len, &r, &total);
- if(r==NULL){
- while(result){
- r = result;
- result = result->next;
- pkg_free(r);
+ f = result;
+ while(f)
+ {
+ if((avp->name.len==f->s.len)&&
+ (strncmp(avp->name.s, f->s.s, f->s.len)==0))
+ {
+ break; /* name already on list */
+ }
+ f = f->next;
+ }
+ if (f==NULL)
+ {
+ r = append_str_list(avp->name.s, avp->name.len, &r, &total);
+ if(r==NULL){
+ while(result){
+ r = result;
+ result = result->next;
+ pkg_free(r);
+ }
+ return 0;
}
- return 0;
}
avp = avp->next;
}
--
1.7.10.4

@ -15,21 +15,12 @@ It dumps the content of pv_cache on json format.
--- a/modules/debugger/Makefile
+++ b/modules/debugger/Makefile
@@ -7,6 +7,15 @@
NAME=debugger.so
LIBS=
+BUILDER = $(shell which pkg-config)
+ifeq ($(BUILDER),)
+ DEFS+=-I/usr/include/json -I$(LOCALBASE)/include/json \
+ -I$(LOCALBASE)/include
+ LIBS+=-L$(SYSBASE)/include/lib -L$(LOCALBASE)/lib -ljson
+else
+ DEFS+= $(shell pkg-config --cflags json)
+ LIBS+= $(shell pkg-config --libs json)
+endif
@@ -9,4 +9,6 @@
DEFS+=-DKAMAILIO_MOD_INTERFACE
+SERLIBPATH=../../lib
+SER_LIBS+=$(SERLIBPATH)/srutils/srutils
include ../../Makefile.modules
--- a/modules/debugger/README
+++ b/modules/debugger/README
@ -46,7 +37,7 @@ It dumps the content of pv_cache on json format.
4. Functions
4.1. dbg_breakpoint(mode)
+ 4.2. dbg_pv_dump()
+ 4.2. dbg_pv_dump(mask)
5. Exported RPC Functions
@ -62,7 +53,7 @@ It dumps the content of pv_cache on json format.
4. Functions
4.1. dbg_breakpoint(mode)
+ 4.2. dbg_pv_dump()
+ 4.2. dbg_pv_dump(mask)
5. Exported RPC Functions
@ -70,32 +61,43 @@ It dumps the content of pv_cache on json format.
4. Functions
4.1. dbg_breakpoint(mode)
+ 4.2. dbg_pv_dump()
+ 4.2. dbg_pv_dump(mask)
4.1. dbg_breakpoint(mode)
@@ -242,6 +246,26 @@
@@ -242,6 +246,37 @@
dbg_breakpoint("1");
...
+4.2. dbg_pv_dump()
+4.2. dbg_pv_dump(mask)
+
+ Prints the content of pv_cache on json format
+ Prints the content of pv_cache on json format controled by mask value
+#define DBG_DP_NULL 1
+#define DBG_DP_AVP 2
+#define DBG_DP_SCRIPTVAR 4
+#define DBG_DP_OTHER 8
+
+ Example 1.9. dbg_pv_dump usage
+...
+$var(temp) = 2;
+$var(temp) = 1;
+$avp(s:more_avp) = 2;
+$avp(s:more_avp) = 3;
+$xavp(x=>more) = "bye";
+$xavp(x[0]=>more) = "hi";
+$xavp(x[0]=>other) = 1;
+$xavp(x[0]=>other) = 2;
+$xavp(x=>different) = "foo";
+$var(empty) = $null;
+
+dbg_pv_dump();
+dbg_pv_dump(14);
+...
+
+ Output
+...
+ 4(24310) DEBUG: debugger [debugger_api.c:1335]: dbg_dump_json(): { "$var(temp)
+": 2, "$avp(s:more_avp)": 3, "$xavp(x[0]=>more)": "hi" }
+
+ 4(30943) DEBUG: debugger [debugger_api.c:1613]: dbg_dump_json(): {"$sp":37597,"
+$var(rc)":0,"$var(temp)":1,"$avp(more_avp)":[3,2],"$si":"127.0.0.1","$rc":0,"$xa
+vp(x)":[{"different":["foo"]},{"other":[2,1],"more":["hi","bye"]}],"$T_branch_id
+x":0,"$var(empty)":0}
+ ...
+
5. Exported RPC Functions
@ -103,70 +105,360 @@ It dumps the content of pv_cache on json format.
5.1. dbg.ls
--- a/modules/debugger/debugger_api.c
+++ b/modules/debugger/debugger_api.c
@@ -25,6 +25,7 @@
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
+#include <json.h>
#include "../../dprint.h"
#include "../../ut.h"
@@ -1078,3 +1079,56 @@
@@ -36,7 +36,10 @@
#include "../../route_struct.h"
#include "../../mem/shm_mem.h"
#include "../../locking.h"
-
+#include "../../lib/srutils/srjson.h"
+#include "../../xavp.h"
+#include "../pv/pv_xavp.h"
+
#include "debugger_act.h"
#include "debugger_api.h"
@@ -1078,3 +1081,342 @@
return;
set_module_debug_level_cb(dbg_get_mod_debug_level);
}
+
+int dbg_dump_json(struct sip_msg* msg, pv_elem_p list)
+int _dbg_get_array_avp_vals(struct sip_msg *msg,
+ pv_param_t *param, srjson_doc_t *jdoc, srjson_t **jobj,
+ str *item_name)
+{
+ pv_value_t value;
+ pv_elem_p el = list;
+ json_object * jobj = NULL;
+ json_object *jstring = NULL;
+ json_object *jint = NULL;
+ char sname[100];
+ char svalue[100];
+
+ jobj = json_object_new_object();
+ if(jobj==NULL){
+ LM_ERR("Can not create a new json object\n");
+ struct usr_avp *avp;
+ unsigned short name_type;
+ int_str avp_name;
+ int_str avp_value;
+ struct search_state state;
+ srjson_t *jobjt;
+ memset(&state, 0, sizeof(struct search_state));
+
+ if(pv_get_avp_name(msg, param, &avp_name, &name_type)!=0)
+ {
+ LM_ERR("invalid name\n");
+ return -1;
+ }
+ *jobj = srjson_CreateArray(jdoc);
+ if(*jobj==NULL)
+ {
+ LM_ERR("cannot create json object\n");
+ return -1;
+ }
+ if ((avp=search_first_avp(name_type, avp_name, &avp_value, &state))==0)
+ {
+ goto ok;
+ }
+ do
+ {
+ if(avp->flags & AVP_VAL_STR)
+ {
+ jobjt = srjson_CreateStr(jdoc, avp_value.s.s, avp_value.s.len);
+ if(jobjt==NULL)
+ {
+ LM_ERR("cannot create json object\n");
+ return -1;
+ }
+ } else {
+ jobjt = srjson_CreateNumber(jdoc, avp_value.n);
+ if(jobjt==NULL)
+ {
+ LM_ERR("cannot create json object\n");
+ return -1;
+ }
+ }
+ srjson_AddItemToArray(jdoc, *jobj, jobjt);
+ } while ((avp=search_next_avp(&state, &avp_value))!=0);
+ok:
+ item_name->s = avp_name.s.s;
+ item_name->len = avp_name.s.len;
+ return 0;
+}
+#define DBG_XAVP_DUMP_SIZE 32
+static str* _dbg_xavp_dump[DBG_XAVP_DUMP_SIZE];
+int _dbg_xavp_dump_lookup(pv_param_t *param)
+{
+ unsigned int i = 0;
+ pv_xavp_name_t *xname;
+
+ if(param==NULL)
+ return -1;
+
+ xname = (pv_xavp_name_t*)param->pvn.u.dname;
+
+ while(_dbg_xavp_dump[i]!=NULL&&i<DBG_XAVP_DUMP_SIZE)
+ {
+ if(_dbg_xavp_dump[i]->len==xname->name.len)
+ {
+ if(strncmp(_dbg_xavp_dump[i]->s, xname->name.s, xname->name.len)==0)
+ return 1; /* already dump before */
+ }
+ i++;
+ }
+ if(i==DBG_XAVP_DUMP_SIZE)
+ {
+ LM_WARN("full _dbg_xavp_dump cache array\n");
+ return 0; /* end cache names */
+ }
+ _dbg_xavp_dump[i] = &xname->name;
+ return 0;
+}
+
+void _dbg_get_obj_xavp_val(sr_xavp_t *avp, srjson_doc_t *jdoc, srjson_t **jobj)
+{
+ static char _pv_xavp_buf[128];
+ int result = 0;
+
+ switch(avp->val.type) {
+ case SR_XTYPE_NULL:
+ *jobj = srjson_CreateNull(jdoc);
+ break;
+ case SR_XTYPE_INT:
+ *jobj = srjson_CreateNumber(jdoc, avp->val.v.i);
+ break;
+ case SR_XTYPE_STR:
+ *jobj = srjson_CreateStr(jdoc, avp->val.v.s.s, avp->val.v.s.len);
+ break;
+ case SR_XTYPE_TIME:
+ result = snprintf(_pv_xavp_buf, 128, "%lu", (long unsigned)avp->val.v.t);
+ break;
+ case SR_XTYPE_LONG:
+ result = snprintf(_pv_xavp_buf, 128, "%ld", (long unsigned)avp->val.v.l);
+ break;
+ case SR_XTYPE_LLONG:
+ result = snprintf(_pv_xavp_buf, 128, "%lld", avp->val.v.ll);
+ break;
+ case SR_XTYPE_XAVP:
+ result = snprintf(_pv_xavp_buf, 128, "<<xavp:%p>>", avp->val.v.xavp);
+ break;
+ case SR_XTYPE_DATA:
+ result = snprintf(_pv_xavp_buf, 128, "<<data:%p>>", avp->val.v.data);
+ break;
+ default:
+ LM_WARN("unknown data type\n");
+ *jobj = srjson_CreateNull(jdoc);
+ }
+ if(result<0)
+ {
+ LM_ERR("cannot convert to str\n");
+ *jobj = srjson_CreateNull(jdoc);
+ }
+ else if(*jobj==NULL)
+ {
+ *jobj = srjson_CreateStr(jdoc, _pv_xavp_buf, 128);
+ }
+}
+
+int _dbg_get_obj_avp_vals(str name, sr_xavp_t *xavp, srjson_doc_t *jdoc, srjson_t **jobj)
+{
+ sr_xavp_t *avp = NULL;
+ srjson_t *jobjt = NULL;
+
+ *jobj = srjson_CreateArray(jdoc);
+ if(*jobj==NULL)
+ {
+ LM_ERR("cannot create json object\n");
+ return -1;
+ }
+ avp = xavp;
+ while(avp!=NULL&&!STR_EQ(avp->name,name))
+ {
+ avp = avp->next;
+ }
+ while(avp!=NULL)
+ {
+ _dbg_get_obj_xavp_val(avp, jdoc, &jobjt);
+ srjson_AddItemToArray(jdoc, *jobj, jobjt);
+ jobjt = NULL;
+ avp = xavp_get_next(avp);
+ }
+
+ return 0;
+}
+
+int _dbg_get_obj_xavp_vals(struct sip_msg *msg,
+ pv_param_t *param, srjson_doc_t *jdoc, srjson_t **jobjr,
+ str *item_name)
+{
+ pv_xavp_name_t *xname = (pv_xavp_name_t*)param->pvn.u.dname;
+ sr_xavp_t *xavp = NULL;
+ sr_xavp_t *avp = NULL;
+ srjson_t *jobj = NULL;
+ srjson_t *jobjt = NULL;
+ struct str_list *keys;
+ struct str_list *k;
+
+ *jobjr = srjson_CreateArray(jdoc);
+ if(*jobjr==NULL)
+ {
+ LM_ERR("cannot create json object\n");
+ return -1;
+ }
+ item_name->s = xname->name.s;
+ item_name->len = xname->name.len;
+ xavp = xavp_get_by_index(&xname->name, 0, NULL);
+ do
+ {
+ if(xavp->val.type==SR_XTYPE_XAVP)
+ {
+ avp = xavp->val.v.xavp;
+ jobj = srjson_CreateObject(jdoc);
+ keys = xavp_get_list_key_names(xavp);
+ if(keys!=NULL)
+ {
+ do
+ {
+ _dbg_get_obj_avp_vals(keys->s, avp, jdoc, &jobjt);
+ srjson_AddStrItemToObject(jdoc, jobj, keys->s.s,
+ keys->s.len, jobjt);
+ k = keys;
+ keys = keys->next;
+ pkg_free(k);
+ jobjt = NULL;
+ }while(keys!=NULL);
+ }
+ }
+ srjson_AddItemToArray(jdoc, *jobjr, jobj);
+ jobj = NULL;
+ }while((xavp = xavp_get_next(xavp))!=0);
+
+ return 0;
+}
+
+#define DBG_DP_NULL 1
+#define DBG_DP_AVP 2
+#define DBG_DP_SCRIPTVAR 4
+#define DBG_DP_OTHER 8
+
+int dbg_dump_json(struct sip_msg* msg, pv_elem_p list, unsigned int mask)
+{
+ pv_value_t value;
+ pv_elem_p el = list;
+ srjson_doc_t jdoc;
+ srjson_t *jobj = NULL;
+ char *output = NULL;
+ str item_name = STR_NULL;
+ int result = -1;
+
+ memset(_dbg_xavp_dump, 0, sizeof(str*)*DBG_XAVP_DUMP_SIZE);
+ srjson_InitDoc(&jdoc, NULL);
+ if(jdoc.root==NULL)
+ {
+ jdoc.root = srjson_CreateObject(&jdoc);
+ if(jdoc.root==NULL)
+ {
+ LM_ERR("cannot create json root\n");
+ goto error;
+ }
+ }
+ while(el)
+ {
+ /*LM_DBG("el->spec[%p][%d] el->text[%.*s]\n", el->spec,
+ el->spec->type, el->text.len, el->text.s);*/
+ jobj = NULL;
+ item_name.len = 0;
+ item_name.s = 0;
+ if(!(el->spec->type==PVT_AVP||
+ el->spec->type==PVT_SCRIPTVAR||
+ el->spec->type==PVT_OTHER))
+ el->spec->type==PVT_OTHER)||
+ !((el->spec->type==PVT_AVP&&mask&DBG_DP_AVP)||
+ (el->spec->type==PVT_SCRIPTVAR&&mask&DBG_DP_SCRIPTVAR)||
+ (el->spec->type==PVT_OTHER&&mask&DBG_DP_OTHER))||
+ (el->spec->trans!=NULL))
+ {
+ el = el->next;
+ continue;
+ }
+ if(pv_get_spec_value(msg, el->spec, &value)!=0)
+ if(el->spec->type==PVT_AVP)
+ {
+ LM_ERR("can't get value\n");
+ el = el->next;
+ continue;
+ if(el->spec->pvp.pvi.type==PV_IDX_ALL||
+ (el->spec->pvp.pvi.type==PV_IDX_INT&&el->spec->pvp.pvi.u.ival!=0))
+ {
+ el = el->next;
+ continue;
+ }
+ else
+ {
+ if(_dbg_get_array_avp_vals(msg, &el->spec->pvp, &jdoc, &jobj, &item_name)!=0)
+ {
+ LM_ERR("can't get value[%.*s]\n", el->text.len, el->text.s);
+ el = el->next;
+ continue;
+ }
+ if(srjson_GetArraySize(&jdoc, jobj)==0 && !(mask&DBG_DP_NULL))
+ {
+ el = el->next;
+ continue;
+ }
+ }
+ }
+
+ snprintf(sname, 100, "%.*s", el->text.len, el->text.s);
+ if(value.flags&(PV_VAL_NULL|PV_VAL_EMPTY|PV_VAL_NONE)){
+ jstring = json_object_new_string("$null");
+ json_object_object_add(jobj, sname, jstring);
+ }else if(value.flags&(PV_VAL_INT)){
+ jint = json_object_new_int(value.ri);
+ json_object_object_add(jobj, sname, jint);
+ }else if(value.flags&(PV_VAL_STR)){
+ snprintf(svalue, 100, "%.*s", value.rs.len, value.rs.s);
+ jstring = json_object_new_string(svalue);
+ json_object_object_add(jobj, sname, jstring);
+ else if(el->spec->type==PVT_OTHER&&
+ el->spec->pvp.pvn.type==PV_NAME_PVAR)
+ {
+ if(_dbg_xavp_dump_lookup(&el->spec->pvp)!=0)
+ {
+ el = el->next;
+ continue;
+ }
+ switch(_dbg_get_obj_xavp_vals(msg, &el->spec->pvp, &jdoc, &jobj, &item_name))
+ {
+ case 1: /* empty */
+ if(!(mask&DBG_DP_NULL))
+ {
+ el = el->next;
+ continue;
+ break;
+ }
+ case -1:
+ LM_ERR("can't get value[%.*s]\n", el->text.len, el->text.s);
+ el = el->next;
+ continue;
+ break;
+ }
+ }
+ else
+ {
+ if(pv_get_spec_value(msg, el->spec, &value)!=0)
+ {
+ LM_ERR("can't get value[%.*s]\n", el->text.len, el->text.s);
+ el = el->next;
+ continue;
+ }
+ if(value.flags&(PV_VAL_NULL|PV_VAL_EMPTY|PV_VAL_NONE)
+ && mask&DBG_DP_NULL){
+ jobj = srjson_CreateNull(&jdoc);
+ }else if(value.flags&(PV_VAL_INT)){
+ jobj = srjson_CreateNumber(&jdoc, value.ri);
+ }else if(value.flags&(PV_VAL_STR)){
+ jobj = srjson_CreateStr(&jdoc, value.rs.s, value.rs.len);
+ }
+ item_name.s = el->text.s;
+ item_name.len = el->text.len;
+ if(jobj==NULL)
+ {
+ LM_ERR("cannot create json object\n");
+ goto error;
+ }
+ }
+ if(jobj!=NULL)
+ {
+ srjson_AddStrItemToObject(&jdoc, jdoc.root,
+ item_name.s, item_name.len, jobj);
+ }
+ el = el->next;
+ }
+ LM_DBG("%s\n", json_object_to_json_string(jobj));
+ json_object_put(jobj);
+ return 0;
+ output = srjson_PrintUnformatted(&jdoc, jdoc.root);
+ if(output==NULL)
+ {
+ LM_ERR("cannot print json doc\n");
+ goto error;
+ }
+ LM_DBG("%s\n", output);
+ result = 0;
+
+error:
+ if(output!=NULL) jdoc.free_fn(output);
+ srjson_DestroyDoc(&jdoc);
+
+ return result;
+}
--- a/modules/debugger/debugger_api.h
+++ b/modules/debugger/debugger_api.h
@ -174,7 +466,7 @@ It dumps the content of pv_cache on json format.
int dbg_set_mod_debug_level(char *mname, int mnlen, int *mlevel);
void dbg_enable_mod_levels(void);
+int dbg_dump_json(struct sip_msg* msg, pv_elem_p list);
+int dbg_dump_json(struct sip_msg* msg, pv_elem_p list, unsigned int mask);
#endif
--- a/modules/debugger/debugger_mod.c
@ -192,8 +484,8 @@ It dumps the content of pv_cache on json format.
static cmd_export_t cmds[]={
{"dbg_breakpoint", (cmd_function)w_dbg_breakpoint, 1,
fixup_dbg_breakpoint, 0, ANY_ROUTE},
+ {"dbg_pv_dump", (cmd_function)w_dbg_dump, 0,
+ 0, 0, ANY_ROUTE},
+ {"dbg_pv_dump", (cmd_function)w_dbg_dump, 1,
+ fixup_uint_null, 0, ANY_ROUTE},
{0, 0, 0, 0, 0, 0}
};
@ -201,11 +493,11 @@ It dumps the content of pv_cache on json format.
return 1;
}
+static int w_dbg_dump(struct sip_msg* msg, char* point, char* str2)
+static int w_dbg_dump(struct sip_msg* msg, char* mask, char* str2)
+{
+ pv_elem_t *list = pv_cache_dump(msg);
+ if(list==NULL) return 0;
+ dbg_dump_json(msg, list);
+ dbg_dump_json(msg, list, (unsigned int)(unsigned long)mask);
+ pv_elem_free_all(list);
+ return 1;
+}
@ -215,7 +507,7 @@ It dumps the content of pv_cache on json format.
*/
--- a/modules/debugger/doc/debugger_admin.xml
+++ b/modules/debugger/doc/debugger_admin.xml
@@ -250,7 +250,35 @@
@@ -250,7 +250,46 @@
</programlisting>
</example>
</section>
@ -223,27 +515,38 @@ It dumps the content of pv_cache on json format.
+
+ <section>
+ <title>
+ <function moreinfo="none">dbg_pv_dump()</function>
+ <function moreinfo="none">dbg_pv_dump(mask)</function>
+ </title>
+ <para>
+ Prints the content of pv_cache on json format
+ Prints the content of pv_cache on json format controled by mask value
+ </para>
+ <programlisting format="linespecific">
+#define DBG_DP_NULL 1
+#define DBG_DP_AVP 2
+#define DBG_DP_SCRIPTVAR 4
+#define DBG_DP_OTHER 8
+ </programlisting>
+ <example>
+ <title><function>dbg_pv_dump</function> usage</title>
+ <programlisting format="linespecific">
+...
+$var(temp) = 2;
+$var(temp) = 1;
+$avp(s:more_avp) = 2;
+$avp(s:more_avp) = 3;
+$xavp(x=>more) = "bye";
+$xavp(x[0]=>more) = "hi";
+$xavp(x[0]=>other) = 1;
+$xavp(x[0]=>other) = 2;
+$xavp(x=>different) = "foo";
+$var(empty) = $null;
+
+dbg_pv_dump();
+dbg_pv_dump(14);
+...
+</programlisting>
+ <para>Output</para>
+ <programlisting format="linespecific">
+...
+ 4(24310) DEBUG: debugger [debugger_api.c:1335]: dbg_dump_json(): { "$var(temp)": 2, "$avp(s:more_avp)": 3, "$xavp(x[0]=>more)": "hi" }
+
+ 4(30943) DEBUG: debugger [debugger_api.c:1613]: dbg_dump_json(): {"$sp":37597,"$var(rc)":0,"$var(temp)":1,"$avp(more_avp)":[3,2],"$si":"127.0.0.1","$rc":0,"$xavp(x)":[{"different":["foo"]},{"other":[2,1],"more":["hi","bye"]}],"$T_branch_idx":0,"$var(empty)":0}
+ ...
+</programlisting>
+ </example>

Loading…
Cancel
Save