diff options
author | Leonardo Arena <rnalrd@alpinelinux.org> | 2015-02-24 14:19:05 +0000 |
---|---|---|
committer | Leonardo Arena <rnalrd@alpinelinux.org> | 2015-02-24 14:19:23 +0000 |
commit | 2bf3eba2ae475597bb7f20748e76b395bd68bbcd (patch) | |
tree | 89dc42297582aea0b36671ad9c947ce4cd06fcc7 | |
parent | e211cfc6cb2183dafc51fd8a6a0562cdf4ce7612 (diff) | |
download | aports-2bf3eba2ae475597bb7f20748e76b395bd68bbcd.tar.bz2 aports-2bf3eba2ae475597bb7f20748e76b395bd68bbcd.tar.xz |
main/kamailio: add srv_query function to ipops module
-rw-r--r-- | main/kamailio/APKBUILD | 7 | ||||
-rw-r--r-- | main/kamailio/kamailio-4.2-ipops-srv-query.patch | 658 |
2 files changed, 664 insertions, 1 deletions
diff --git a/main/kamailio/APKBUILD b/main/kamailio/APKBUILD index 5498eb7ae4..a5802c2d17 100644 --- a/main/kamailio/APKBUILD +++ b/main/kamailio/APKBUILD @@ -13,7 +13,7 @@ _gittag=HEAD pkgver=4.2.3 -pkgrel=0 +pkgrel=1 [ -z "${_gitcommit}" ] && _suffix="_src" || _suffix="-${_gitcommit}" pkgdesc="Open Source SIP Server" @@ -225,6 +225,8 @@ done source="http://www.kamailio.org/pub/kamailio/$pkgver/src/kamailio-${pkgver}${_suffix}.tar.gz kamailio-4.2-backslash.patch 0001-musl-fixes.patch + kamailio-4.2-ipops-srv-query.patch + kamailio.cfg kamailio.initd " @@ -489,15 +491,18 @@ redis() { md5sums="f94eb1db3820dba22bd3fdae464e93b3 kamailio-4.2.3_src.tar.gz bad1ac2d4c95043df271d2ea6d37627a kamailio-4.2-backslash.patch 4685288dc54680597b00f956dc95d4d6 0001-musl-fixes.patch +5b7ecf5c4ae06420c028e03721cb9e89 kamailio-4.2-ipops-srv-query.patch a3c959ec568c43a905710e7d25cd8c25 kamailio.cfg 0e0a271fd3ddb7e87c01c26c7d041d59 kamailio.initd" sha256sums="7dbbca4a515778d3e903380adcc49f727ddc4853238cb905e14c811a5671ed80 kamailio-4.2.3_src.tar.gz d7e59be721ed0ad4621d404493b9a519708d801e9d4914b0164b819fa1abcd13 kamailio-4.2-backslash.patch b98555ff304b51b82c6cf7e01d757b15ea4f05bd2e603c84d4384df6a6be62b6 0001-musl-fixes.patch +cfe645fc80eaed8a9e4bd56047f75555b2a9e3edcb3e2b6c6cece1547ab0a574 kamailio-4.2-ipops-srv-query.patch 8024266849033a917147827c3579a382f10f3796989bebc6de3d7c80c965fb72 kamailio.cfg a90d3ab09a3ed58892e94710a1f80492a61ffad1ccf7ccb5b851bb8f538d32c4 kamailio.initd" sha512sums="2f42499fe84eefac236fe3d4aa3c7bc424944236f00b95a7071feaa816b3df5764f84076d57b2137908dab7ff06a2440cc7a53a799216befd9511f8718a2eee5 kamailio-4.2.3_src.tar.gz a9bb1e8f9f373264b8351ddae099a36a46ddd46fdec09e468d297ba4f64bb4896e7d6e599da70a424e8a28695ab3f3b4ac940afab534593a6b9d08ae462f001a kamailio-4.2-backslash.patch dea7ef2ccf01357576045ba375d41301e2447b4454324007c7ca1862322835c57045852017192ca5434b32dd1b7a2e9669209b7111889dab335b74f042d0f11f 0001-musl-fixes.patch +3a9bb5d05b4628f6146b824b8916db259f1da51415398ba420900311d73986d21cab653079080c0e8c55ef512909f542279ca7da944b5ef14520331584ca958f kamailio-4.2-ipops-srv-query.patch 0b666bfa10fd0af97b62749f8691cb3f76d9b40d1abe0a33e810e367bd733d2e8189c89f7f23010ec591116aada6e1a8a403b17449fe775038917617f281ad4d kamailio.cfg 5ddaa059cdef10462c904f061f7bb085e62ad7501e2ed41f797d9e68822bce4e0e5ca09c1586c3901c920f8ce563c8c3ede860752c2b9bdb8f09908388ef337f kamailio.initd" diff --git a/main/kamailio/kamailio-4.2-ipops-srv-query.patch b/main/kamailio/kamailio-4.2-ipops-srv-query.patch new file mode 100644 index 0000000000..450215e112 --- /dev/null +++ b/main/kamailio/kamailio-4.2-ipops-srv-query.patch @@ -0,0 +1,658 @@ +--- a/modules/ipops/ipops_mod.c ++++ b/modules/ipops/ipops_mod.c +@@ -21,6 +21,7 @@ + * + * History: + * ------- ++ * 2015-02-04: Added srv_query function (rboisvert) + * 2011-07-29: Added a function to detect RFC1918 private IPv4 addresses (ibc) + * 2011-04-27: Initial version (ibc) + */ +@@ -92,10 +93,13 @@ + static int w_dns_int_match_ip(sip_msg_t*, char*, char*); + + static int w_dns_query(struct sip_msg* msg, char* str1, char* str2); ++static int w_srv_query(struct sip_msg* msg, char* str1, char* str2); + + static pv_export_t mod_pvs[] = { + { {"dns", sizeof("dns")-1}, PVT_OTHER, pv_get_dns, 0, + pv_parse_dns_name, 0, 0, 0 }, ++ { {"srvquery", sizeof("srvquery")-1}, PVT_OTHER, pv_get_srv, 0, ++ pv_parse_srv_name, 0, 0, 0 }, + { {"HN", sizeof("HN")-1}, PVT_OTHER, pv_get_hn, 0, + pv_parse_hn_name, 0, 0, 0 }, + { {0, 0}, 0, 0, 0, 0, 0, 0, 0 } +@@ -132,6 +136,8 @@ + ANY_ROUTE }, + { "dns_query", (cmd_function)w_dns_query, 2, fixup_spve_spve, 0, + ANY_ROUTE }, ++ { "srv_query", (cmd_function)w_srv_query, 2, fixup_spve_spve, 0, ++ ANY_ROUTE }, + { "bind_ipops", (cmd_function)bind_ipops, 0, 0, 0, 0}, + { 0, 0, 0, 0, 0, 0 } + }; +@@ -772,4 +778,32 @@ + } + + return dns_update_pv(&hostname, &name); ++} ++ ++/** ++ * ++ */ ++static int w_srv_query(struct sip_msg* msg, char* str1, char* str2) ++{ ++ str srvcname; ++ str name; ++ ++ if(msg==NULL) ++ { ++ LM_ERR("received null msg\n"); ++ return -1; ++ } ++ ++ if(fixup_get_svalue(msg, (gparam_t*)str1, &srvcname)<0) ++ { ++ LM_ERR("cannot get the srvcname\n"); ++ return -1; ++ } ++ if(fixup_get_svalue(msg, (gparam_t*)str2, &name)<0) ++ { ++ LM_ERR("cannot get the pvid name\n"); ++ return -1; ++ } ++ ++ return srv_update_pv(&srvcname, &name); + } +--- a/modules/ipops/ipops_pv.c ++++ b/modules/ipops/ipops_pv.c +@@ -32,6 +32,7 @@ + #include <netinet/in.h> + + #include "../../dprint.h" ++#include "../../rand/fastrand.h" + #include "../../hashes.h" + #include "../../resolve.h" + #include "../../pvar.h" +@@ -66,7 +67,6 @@ + int nidx; + } dns_pv_t; + +- + static sr_dns_item_t *_sr_dns_list = NULL; + + /** +@@ -132,7 +132,6 @@ + return it; + } + +- + /** + * + */ +@@ -431,7 +430,6 @@ + return 1; + } + +- + struct _hn_pv_data { + str data; + str fullname; +@@ -594,4 +592,545 @@ + return pv_get_null(msg, param, res);; + return pv_get_strval(msg, param, res, &_hn_data->hostname); + } ++} ++ ++/********** ++* srvquery PV ++**********/ ++ ++static char *srvqrylst [] ++ = {"count", "port", "priority", "target", "weight", NULL}; ++ ++#define PV_SRV_MAXSTR 64 ++#define PV_SRV_MAXRECS 32 ++ ++typedef struct _sr_srv_record { ++ unsigned short priority; ++ unsigned short weight; ++ unsigned short port; ++ char target [PV_SRV_MAXSTR + 1]; ++} sr_srv_record_t; ++ ++typedef struct _sr_srv_item { ++ str pvid; ++ unsigned int hashid; ++ int count; ++ sr_srv_record_t rr [PV_SRV_MAXRECS]; ++ struct _sr_srv_item *next; ++} sr_srv_item_t; ++ ++typedef struct _srv_pv { ++ sr_srv_item_t *item; ++ int type; ++ int flags; ++ pv_spec_t *pidx; ++ int nidx; ++} srv_pv_t; ++ ++static sr_srv_item_t *_sr_srv_list = NULL; ++ ++/********** ++* Add srvquery Item ++* ++* INPUT: ++* Arg (1) = pvid string pointer ++* Arg (2) = find flag; <>0=search only ++* OUTPUT: srv record pointer; NULL=not found ++**********/ ++ ++sr_srv_item_t *sr_srv_add_item (str *pvid, int findflg) ++ ++{ ++sr_srv_item_t *pitem; ++unsigned int hashid; ++ ++/********** ++* o get hash ++* o already exists? ++**********/ ++ ++hashid = get_hash1_raw (pvid->s, pvid->len); ++for (pitem = _sr_srv_list; pitem; pitem = pitem->next) { ++ if (pitem->hashid == hashid ++ && pitem->pvid.len == pvid->len ++ && !strncmp (pitem->pvid.s, pvid->s, pvid->len)) ++ return pitem; ++} ++if (findflg) ++ return NULL; ++ ++/********** ++* o alloc/init item structure ++* o link in new item ++**********/ ++ ++pitem = (sr_srv_item_t *) pkg_malloc (sizeof (sr_srv_item_t)); ++if (!pitem) { ++ LM_ERR ("No more pkg memory!\n"); ++ return NULL; ++} ++memset (pitem, 0, sizeof (sr_srv_item_t)); ++pitem->pvid.s = (char *) pkg_malloc (pvid->len + 1); ++if (!pitem->pvid.s) { ++ LM_ERR ("No more pkg memory!\n"); ++ pkg_free (pitem); ++ return NULL; ++} ++memcpy (pitem->pvid.s, pvid->s, pvid->len); ++pitem->pvid.len = pvid->len; ++pitem->hashid = hashid; ++pitem->next = _sr_srv_list; ++_sr_srv_list = pitem; ++return pitem; ++} ++ ++/********** ++* Skip Over ++* ++* INPUT: ++* Arg (1) = string pointer ++* Arg (2) = starting position ++* Arg (3) = whitespace flag ++* OUTPUT: position past skipped ++**********/ ++ ++int skip_over (str *pstr, int pos, int bWS) ++ ++{ ++char *pchar; ++ ++/********** ++* o string exists? ++* o skip over ++**********/ ++ ++if (pos >= pstr->len) ++ return pstr->len; ++for (pchar = &pstr->s [pos]; pos < pstr->len; pchar++, pos++) { ++ if (*pchar == ' ' || *pchar == '\t' || *pchar == '\n' || *pchar == '\r') { ++ if (bWS) ++ continue; ++ } ++ if ((*pchar>='A' && *pchar<='Z') || (*pchar>='a' && *pchar<='z') ++ || (*pchar>='0' && *pchar<='9')) { ++ if (!bWS) ++ continue; ++ } ++ break; ++} ++return pos; ++} ++ ++/********** ++* Sort SRV Records by Weight (RFC 2782) ++* ++* INPUT: ++* Arg (1) = pointer to array of SRV records ++* Arg (2) = first record in range ++* Arg (3) = last record in range ++* OUTPUT: position past skipped ++**********/ ++ ++void sort_weights (struct srv_rdata **plist, int pos1, int pos2) ++ ++{ ++int idx1, idx2, idx3, lastfound; ++struct srv_rdata *wlist [PV_SRV_MAXRECS]; ++unsigned int rand, sum, sums [PV_SRV_MAXRECS]; ++ ++/********** ++* place zero weights in the unordered list and then non-zero ++**********/ ++ ++idx2 = 0; ++for (idx1 = pos1; idx1 <= pos2; idx1++) { ++ if (!plist [idx1]->weight) { ++ wlist [idx2++] = plist [idx1]; ++ } ++} ++for (idx1 = pos1; idx1 <= pos2; idx1++) { ++ if (plist [idx1]->weight) { ++ wlist [idx2++] = plist [idx1]; ++ } ++} ++ ++/********** ++* generate running sum list ++**********/ ++ ++sum = 0; ++for (idx1 = pos1; idx1 <= pos2; idx1++) { ++ sum += wlist [idx1]->weight; ++ sums [pos1 - idx1] = sum; ++} ++ ++/********** ++* resort randomly ++**********/ ++ ++idx3 = pos1; ++lastfound = 0; ++for (idx1 = pos2 - pos1; idx1; --idx1) { ++ /********** ++ * o calculate a random number in range ++ * o find first unsorted ++ **********/ ++ ++ rand = fastrand_max (sum); ++ for (idx2 = 0; idx2 < pos2 - pos1; idx2++) { ++ if (!wlist [idx2]) { ++ continue; ++ } ++ if (sums [idx2] >= rand) { ++ plist [idx3++] = wlist [idx2]; ++ wlist [idx2] = 0; ++ break; ++ } ++ lastfound = idx2; ++ } ++ if (idx2 == pos2 - pos1) { ++ plist [idx3++] = wlist [lastfound]; ++ wlist [lastfound] = 0; ++ } ++} ++return; ++} ++ ++/********** ++* Sort SRV Records by Priority/Weight ++* ++* INPUT: ++* Arg (1) = pointer to array of SRV records ++* Arg (2) = record count ++* OUTPUT: position past skipped ++**********/ ++ ++void sort_srv (struct srv_rdata **plist, int rcount) ++ ++{ ++int idx1, idx2; ++struct srv_rdata *pswap; ++ ++/********** ++* sort by priority ++**********/ ++ ++for (idx1 = 1; idx1 < rcount; idx1++) { ++ pswap = plist [idx1]; ++ for (idx2 = idx1; ++ idx2 && (plist [idx2 - 1]->priority > pswap->priority); --idx2) { ++ plist [idx2] = plist [idx2 - 1]; ++ } ++ plist [idx2] = pswap; ++} ++ ++/********** ++* check for multiple priority ++**********/ ++ ++idx2 = 0; ++pswap = plist [0]; ++for (idx1 = 1; idx1 <= rcount; idx1++) { ++ if ((idx1 == rcount) || (pswap->priority != plist [idx1]->priority)) { ++ /********** ++ * o range has more than one element? ++ * o restart range ++ **********/ ++ ++ if (idx1 - idx2 - 1) { ++ sort_weights (plist, idx2, idx1 - 1); ++ } ++ idx2 = idx1; ++ pswap = plist [idx2]; ++ } ++} ++return; ++} ++ ++/********** ++* Parse srvquery Name ++* ++* INPUT: ++* Arg (1) = pv spec pointer ++* Arg (2) = input string pointer ++* OUTPUT: 0=success ++**********/ ++ ++int pv_parse_srv_name (pv_spec_t *sp, str *in) ++ ++{ ++char *pstr; ++int i, pos, sign; ++srv_pv_t *dpv; ++str pvi, pvk, pvn; ++ ++/********** ++* o alloc/init pvid structure ++* o extract pvid name ++* o check separator ++**********/ ++ ++if (!sp || !in || in->len<=0) ++ return -1; ++dpv = (srv_pv_t *) pkg_malloc (sizeof (srv_pv_t)); ++if (!dpv) { ++ LM_ERR ("No more pkg memory!\n"); ++ return -1; ++} ++memset (dpv, 0, sizeof (srv_pv_t)); ++pos = skip_over (in, 0, 1); ++if (pos == in->len) ++ goto error; ++pvn.s = &in->s [pos]; ++pvn.len = pos; ++pos = skip_over (in, pos, 0); ++pvn.len = pos - pvn.len; ++if (!pvn.len) ++ goto error; ++pos = skip_over (in, pos, 1); ++if ((pos + 2) > in->len) ++ goto error; ++if (strncmp (&in->s [pos], "=>", 2)) ++ goto error; ++ ++/********** ++* o extract key name ++* o check key name ++* o count? ++**********/ ++ ++pos = skip_over (in, pos + 2, 1); ++pvk.s = &in->s [pos]; ++pvk.len = pos; ++pos = skip_over (in, pos, 0); ++pvk.len = pos - pvk.len; ++if (!pvk.len) ++ goto error; ++for (i = 0; srvqrylst [i]; i++) { ++ if (strlen (srvqrylst [i]) != pvk.len) ++ continue; ++ if (!strncmp (pvk.s, srvqrylst [i], pvk.len)) { ++ dpv->type = i; ++ break; ++ } ++} ++if (!srvqrylst [i]) ++ goto error; ++if (!i) ++ goto noindex; ++ ++/********** ++* o check for array ++* o extract array index and check ++**********/ ++ ++pos = skip_over (in, pos, 1); ++if ((pos + 3) > in->len) ++ goto error; ++if (in->s [pos] != '[') ++ goto error; ++pos = skip_over (in, pos + 1, 1); ++if ((pos + 2) > in->len) ++ goto error; ++pvi.s = &in->s [pos]; ++pvi.len = pos; ++if (in->s [pos] == PV_MARKER) { ++ /********** ++ * o search from the end back to array close ++ * o get PV value ++ **********/ ++ ++ for (i = in->len - 1; i != pos; --i) { ++ if (in->s [i] == ']') ++ break; ++ } ++ if (i == pos) ++ goto error; ++ pvi.len = i - pvi.len; ++ pos = i + 1; ++ dpv->pidx = pv_cache_get (&pvi); ++ if (!dpv->pidx) ++ goto error; ++ dpv->flags |= SR_DNS_PVIDX; ++} else { ++ /********** ++ * o get index value ++ * o check for reverse index ++ * o convert string to number ++ **********/ ++ ++ pos = skip_over (in, pos, 0); ++ pvi.len = pos - pvi.len; ++ sign = 1; ++ i = 0; ++ pstr = pvi.s; ++ if (*pstr == '-') { ++ sign = -1; ++ i++; ++ pstr++; ++ } ++ for (dpv->nidx = 0; i < pvi.len; i++) { ++ if (*pstr >= '0' && *pstr <= '9') ++ dpv->nidx = (dpv->nidx * 10) + *pstr++ - '0'; ++ } ++ if (i != pvi.len) ++ goto error; ++ dpv->nidx *= sign; ++ pos = skip_over (in, pos, 1); ++ if (pos == in->len) ++ goto error; ++ if (in->s [pos++] != ']') ++ goto error; ++} ++ ++/********** ++* o check for trailing whitespace ++* o add data to PV ++**********/ ++ ++noindex: ++if (skip_over (in, pos, 1) != in->len) ++ goto error; ++LM_DBG ("srvquery (%.*s => %.*s [%.*s])\n", ++ pvn.len, pvn.s, pvk.len, pvk.s, pvi.len, pvi.s); ++dpv->item = sr_srv_add_item (&pvn, 0); ++if (!dpv->item) ++ goto error; ++sp->pvp.pvn.u.dname = (void *)dpv; ++sp->pvp.pvn.type = PV_NAME_OTHER; ++return 0; ++ ++error: ++LM_ERR ("error at PV srvquery: %.*s@%d\n", in->len, in->s, pos); ++pkg_free (dpv); ++return -1; ++} ++ ++int srv_update_pv (str *srvcname, str *pvid) ++ ++{ ++int idx1, idx2, rcount; ++struct rdata *phead, *psrv; ++struct srv_rdata *plist [PV_SRV_MAXRECS]; ++sr_srv_item_t *pitem; ++sr_srv_record_t *prec; ++ ++/********** ++* o service name missing? ++* o find pvid ++**********/ ++ ++if (!srvcname->len) { ++ LM_DBG ("service name missing: %.*s\n", srvcname->len, srvcname->s); ++ return -2; ++} ++pitem = sr_srv_add_item (pvid, 1); ++if (!pitem) { ++ LM_DBG ("pvid not found: %.*s\n", pvid->len, pvid->s); ++ return -3; ++} ++ ++/********** ++* o get records ++* o sort by priority/weight ++* o save to PV ++**********/ ++ ++LM_DBG ("attempting to query: %.*s\n", srvcname->len, srvcname->s); ++phead = get_record (srvcname->s, T_SRV, RES_ONLY_TYPE); ++rcount = 0; ++for (psrv = phead; psrv; psrv = psrv->next) { ++ if (rcount < PV_SRV_MAXRECS) { ++ plist [rcount++] = (struct srv_rdata *) psrv->rdata; ++ } else { ++ LM_WARN ("truncating srv_query list to %d records!", PV_SRV_MAXRECS); ++ break; ++ } ++} ++pitem->count = rcount; ++if (rcount) ++ sort_srv (plist, rcount); ++for (idx1 = 0; idx1 < rcount; idx1++) { ++ prec = &pitem->rr [idx1]; ++ prec->priority = plist [idx1]->priority; ++ prec->weight = plist [idx1]->weight; ++ prec->port = plist [idx1]->port; ++ idx2 = plist [idx1]->name_len; ++ if (idx2 > PV_SRV_MAXSTR) { ++ LM_WARN ("truncating srv_query target (%.*s)!", idx2, plist [idx1]->name); ++ idx2 = PV_SRV_MAXSTR; ++ } ++ strncpy (prec->target, plist [idx1]->name, idx2); ++ prec->target [idx2] = '\0'; ++} ++if (phead) ++ free_rdata_list (phead); ++LM_DBG ("srvquery PV updated for: %.*s (%d)\n", ++ srvcname->len, srvcname->s, rcount); ++return 1; ++} ++ ++/********** ++* Get srvquery Values ++* ++* INPUT: ++* Arg (1) = SIP message pointer ++* Arg (2) = parameter pointer ++* Arg (3) = PV value pointer ++* OUTPUT: 0=success ++**********/ ++ ++int pv_get_srv (sip_msg_t *pmsg, pv_param_t *param, pv_value_t *res) ++ ++{ ++pv_value_t val; ++srv_pv_t *dpv; ++ ++/********** ++* o sipmsg and param exist? ++* o PV name exists? ++* o count? ++**********/ ++ ++if(!pmsg || !param) ++ return -1; ++dpv = (srv_pv_t *) param->pvn.u.dname; ++if(!dpv || !dpv->item) ++ return -1; ++if (!dpv->type) ++ return pv_get_sintval (pmsg, param, res, dpv->item->count); ++ ++/********** ++* o get index value ++* o reverse index? ++* o extract data ++**********/ ++ ++if (!dpv->pidx) { ++ val.ri = dpv->nidx; ++} else { ++ if (pv_get_spec_value (pmsg, dpv->pidx, &val) < 0 ++ || !(val.flags & PV_VAL_INT)) { ++ LM_ERR ("failed to evaluate index variable!\n"); ++ return pv_get_null (pmsg, param, res); ++ } ++} ++if (val.ri < 0) { ++ if ((dpv->item->count + val.ri) < 0) ++ return pv_get_null (pmsg, param, res); ++ val.ri = dpv->item->count + val.ri; ++} ++if (val.ri >= dpv->item->count) ++ return pv_get_null(pmsg, param, res); ++switch (dpv->type) { ++ case 1: /* port */ ++ return pv_get_sintval (pmsg, param, res, dpv->item->rr [val.ri].port); ++ case 2: /* priority */ ++ return pv_get_sintval (pmsg, param, res, dpv->item->rr [val.ri].priority); ++ case 3: /* target */ ++ return pv_get_strzval (pmsg, param, res, dpv->item->rr [val.ri].target); ++ case 4: /* weight */ ++ return pv_get_sintval (pmsg, param, res, dpv->item->rr [val.ri].weight); ++} ++return pv_get_null (pmsg, param, res); + } +--- a/modules/ipops/ipops_pv.h ++++ b/modules/ipops/ipops_pv.h +@@ -39,5 +39,9 @@ + int pv_get_hn(struct sip_msg *msg, pv_param_t *param, + pv_value_t *res); + ++int pv_parse_srv_name(pv_spec_t *, str *); ++int pv_get_srv(sip_msg_t *, pv_param_t *, pv_value_t *); ++int srv_update_pv(str *, str *); ++ + #endif + |