blob: 28189aac9ee39642d79f887d1b4f91ea2133af6c [file] [log] [blame]
/*
** Copyright 2003-2004 The Apache Software Foundation
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
#ifndef APREQ_XS_TABLES_H
#define APREQ_XS_TABLES_H
/* backward compatibility macros support */
#include "ppport.h"
#if (PERL_VERSION >= 8) /* MAGIC ITERATOR REQUIRES 5.8 */
/* Requires perl 5.8 or better.
* A custom MGVTBL with its "copy" slot filled allows
* us to FETCH a table entry immediately during iteration.
* For multivalued keys this is essential in order to get
* the value corresponding to the current key, otherwise
* values() will always report the first value repeatedly.
* With this MGVTBL the keys() list always matches up with
* the values() list, even in the multivalued case.
* We only prefetch the value during iteration, because the
* prefetch adds overhead to EXISTS and STORE operations.
* They are only "penalized" when the perl program is iterating
* via each(), which seems to be a reasonable tradeoff.
*/
static int apreq_xs_table_magic_copy(pTHX_ SV *sv, MAGIC *mg, SV *nsv,
const char *name, int namelen)
{
/* Prefetch the value whenever the table iterator is > 0 */
MAGIC *tie_magic = mg_find(nsv, PERL_MAGIC_tiedelem);
SV *obj = SvRV(tie_magic->mg_obj);
if (SvCUR(obj))
SvGETMAGIC(nsv);
return 0;
}
static const MGVTBL apreq_xs_table_magic = {0, 0, 0, 0, 0,
apreq_xs_table_magic_copy};
#endif
/**
* Converts a C object, with environment, to a TIEHASH object.
* @param obj C object.
* @param env C environment.
* @param class Class perl object will be blessed and tied to.
* @return Reference to a new TIEHASH object in class.
*/
APR_INLINE
static SV *apreq_xs_table_c2perl(pTHX_ void *obj, const char *name, I32 nlen,
const char *class, SV *parent, unsigned tainted)
{
SV *sv = (SV *)newHV();
/*upgrade ensures CUR and LEN are both 0 */
SV *rv = sv_setref_pv(newSV(0), class, obj);
sv_magic(SvRV(rv), parent, PERL_MAGIC_ext, name, nlen);
if (tainted)
SvTAINTED_on(SvRV(rv));
#if (PERL_VERSION >= 8) /* MAGIC ITERATOR requires 5.8 */
sv_magic(sv, NULL, PERL_MAGIC_ext, Nullch, -1);
SvMAGIC(sv)->mg_virtual = (MGVTBL *)&apreq_xs_table_magic;
SvMAGIC(sv)->mg_flags |= MGf_COPY;
#endif
sv_magic(sv, rv, PERL_MAGIC_tied, Nullch, 0);
SvREFCNT_dec(rv); /* corrects SvREFCNT_inc(rv) implicit in sv_magic */
return sv_bless(newRV_noinc(sv), SvSTASH(SvRV(rv)));
}
#define apreq_xs_sv2table(sv) ((apr_table_t *) SvIVX(SvRV(sv)))
#define apreq_xs_table2sv(t,class,parent,name,nlen,tainted) \
apreq_xs_table_c2perl(aTHX_ t, name, nlen, class, parent, tainted)
#define APREQ_XS_DEFINE_TABLE_MAKE(attr,pkg, plen) \
static XS(apreq_xs_table_##attr##_make) \
{ \
\
dXSARGS; \
SV *sv, *parent; \
void *env; \
const char *class; \
apr_table_t *t; \
\
if (items != 2 || !SvPOK(ST(0)) || !SvROK(ST(1))) \
Perl_croak(aTHX_ "Usage: $class->make($env)"); \
\
\
class = SvPV_nolen(ST(0)); \
parent = SvRV(ST(1)); \
env = (void *)SvIVX(parent); \
t = apr_table_make(apreq_env_pool(env), APREQ_NELTS); \
sv = apreq_xs_table2sv(t, class, parent, pkg, plen, SvTAINTED(parent)); \
XSprePUSH; \
PUSHs(sv); \
XSRETURN(1); \
}
#define APREQ_XS_DEFINE_TABLE_METHOD_N(attr,method) \
static XS(apreq_xs_table_##attr##_##method) \
{ \
dXSARGS; \
void *env; \
apr_table_t *t; \
const char *key, *val; \
SV *sv, *obj; \
STRLEN klen, vlen; \
apreq_##attr##_t *RETVAL = NULL; \
\
switch (items) { \
case 2: \
case 3: \
if (!SvROK(ST(0))) \
break; \
sv = ST(0); \
obj = apreq_xs_find_obj(aTHX_ sv, #attr); \
env = apreq_xs_sv2env(obj); \
t = (apr_table_t *)SvIVX(obj); \
\
if (SvROK(ST(items-1))) { \
RETVAL = (apreq_##attr##_t *)SvIVX(SvRV(ST(items-1))); \
if (SvTAINTED(SvRV(ST(items-1)))) \
SvTAINTED_on(obj); \
} \
else if (items == 3) { \
key = SvPV(ST(1),klen); \
val = SvPV(ST(2),vlen); \
RETVAL = apreq_make_##attr(apreq_env_pool(env), key, klen, \
val, vlen); \
if (SvTAINTED(ST(1)) || SvTAINTED(ST(2))) \
SvTAINTED_on(obj); \
} \
apr_table_##method##n(t, RETVAL->v.name, RETVAL->v.data); \
XSRETURN_EMPTY; \
default: \
; /* usage */ \
} \
\
Perl_croak(aTHX_ "Usage: $table->" #method \
"([$key,] $val))"); \
}
/* TABLE_GET */
struct apreq_xs_table_key_magic {
SV *obj;
const char *val;
};
struct apreq_xs_do_arg {
void *env;
const char *pkg;
SV *parent, *sub;
unsigned tainted;
PerlInterpreter *perl;
};
static int apreq_xs_table_keys(void *data, const char *key,
const char *val)
{
struct apreq_xs_do_arg *d = (struct apreq_xs_do_arg *)data;
dTHXa(d->perl);
dSP;
SV *sv = newSVpv(key,0);
if (d->tainted)
SvTAINTED_on(sv);
XPUSHs(sv_2mortal(sv));
PUTBACK;
return 1;
}
#define apreq_xs_do(attr) (items == 1 ? apreq_xs_table_keys \
: apreq_xs_##attr##_table_values)
#define apreq_xs_push(attr,sv,d,key) do { \
apr_table_t *t = apreq_xs_##attr##_sv2table(sv); \
if (t != NULL) { \
if (items == 1) { \
t = apr_table_copy(apreq_env_pool(env), t); \
apr_table_compress(t, APR_OVERLAP_TABLES_SET); \
apr_table_do(apreq_xs_table_keys, d, t, NULL); \
} \
else \
apr_table_do(apreq_xs_##attr##_table_values, d, \
t, key, NULL); \
} \
} while (0)
/**
* @param attr obj/attribute name.
* @param class perl class the attribute is in (usually a table class).
* @param type apreq data type: param or cookie.
* @param subclass perl class for returned "type2sv" scalars.
* @param COND expression that must be true for RETVAL to be added
* to the return list.
*
* @remark
* Requires macros for controlling behavior in context:
*
* apreq_xs_##attr##_push G_ARRAY
* apreq_xs_##attr##_sv2table G_SCALAR (items==1)
* apreq_xs_##attr##_##type G_SCALAR (items==2)
* apreq_xs_##type##2sv G_ARRAY and G_SCALAR
*
*/
#define APREQ_XS_DEFINE_TABLE_GET(attr, class, type, subclass, COND) \
static int apreq_xs_##attr##_table_values(void *data, const char *key, \
const char *val) \
{ \
struct apreq_xs_do_arg *d = (struct apreq_xs_do_arg *)data; \
void *env; \
dTHXa(d->perl); \
dSP; \
env = d->env; \
if (val) { \
apreq_##type##_t *RETVAL = \
apreq_value_to_##type(apreq_strtoval(val)); \
if (COND) { \
SV *sv = apreq_xs_##type##2sv(RETVAL,d->pkg,d->parent); \
if (d->tainted) \
SvTAINTED_on(SvROK(sv)? SvRV(sv) : sv); \
XPUSHs(sv_2mortal(sv)); \
} \
} else \
XPUSHs(&PL_sv_undef); \
\
PUTBACK; \
return 1; \
} \
\
static XS(apreq_xs_##attr##_get) \
{ \
dXSARGS; \
const char *key = NULL; \
struct apreq_xs_do_arg d = { NULL, NULL, NULL, NULL, 0, aTHX }; \
void *env; \
SV *sv, *obj; \
MAGIC *mg; \
if (items == 0 || items > 2 || !SvROK(ST(0))) \
Perl_croak(aTHX_ "Usage: $object->get($key)"); \
\
sv = ST(0); \
obj = apreq_xs_find_obj(aTHX_ sv, #attr); \
mg = mg_find(obj, PERL_MAGIC_ext); \
d.parent = mg->mg_obj; \
d.pkg = mg->mg_len > 0 ? mg->mg_ptr : subclass; \
env = (void *)SvIVX(d.parent); \
d.env = env; \
\
d.tainted = SvTAINTED(obj); \
if (items == 2) \
key = SvPV_nolen(ST(1)); \
\
XSprePUSH; \
switch (GIMME_V) { \
apreq_##type##_t *RETVAL; \
\
case G_ARRAY: \
PUTBACK; \
apreq_xs_##attr##_push(obj, &d, key); \
break; \
\
case G_SCALAR: \
if (items == 1) { \
apr_table_t *t = apreq_xs_##attr##_sv2table(obj); \
if (t != NULL) { \
SV *tsv = apreq_xs_table2sv(t, class, d.parent, \
d.pkg, d.pkg ? strlen(d.pkg) : 0, d.tainted); \
PUSHs(sv_2mortal(tsv)); \
} \
PUTBACK; \
break; \
} \
\
RETVAL = apreq_xs_##attr##_##type(obj, key); \
\
if (RETVAL && (COND)) { \
SV *ssv = apreq_xs_##type##2sv(RETVAL, d.pkg, d.parent); \
if (d.tainted) \
SvTAINTED_on(ssv); \
PUSHs(sv_2mortal(ssv)); \
} \
\
default: \
PUTBACK; \
} \
apreq_xs_##attr##_error_check; \
}
#define APREQ_XS_DEFINE_TABLE_FETCH(attr,type,subclass) \
static XS(apreq_xs_##attr##_FETCH) \
{ \
dXSARGS; \
SV *sv, *obj, *parent; \
IV idx; \
MAGIC *mg; \
const char *key, *pkg; \
const char *val; \
apr_table_t *t; \
const apr_array_header_t *arr; \
apr_table_entry_t *te; \
void *env; \
\
if (items != 2 || !SvROK(ST(0)) || !SvOK(ST(1))) \
Perl_croak(aTHX_ "Usage: $table->FETCH($key)"); \
\
sv = ST(0); \
obj = apreq_xs_find_obj(aTHX_ sv, #attr); \
mg = mg_find(obj, PERL_MAGIC_ext); \
parent = mg->mg_obj; \
pkg = mg->mg_len > 0 ? mg->mg_ptr : subclass; \
key = SvPV_nolen(ST(1)); \
idx = SvCUR(obj); \
t = apreq_xs_##attr##_sv2table(obj); \
arr = apr_table_elts(t); \
te = (apr_table_entry_t *)arr->elts; \
env = apreq_xs_##attr##_sv2env(obj); \
\
if (idx > 0 && idx <= arr->nelts \
&& !strcasecmp(key, te[idx-1].key)) \
val = te[idx-1].val; \
else \
val = apr_table_get(t, key); \
\
if (val != NULL) { \
apreq_##type##_t *RETVAL = apreq_value_to_##type( \
apreq_strtoval(val)); \
sv = apreq_xs_##type##2sv(RETVAL, pkg, parent); \
if (SvTAINTED(obj)) \
SvTAINTED_on(sv); \
ST(0) = sv_2mortal(sv); \
XSRETURN(1); \
} \
else \
XSRETURN_UNDEF; \
}
#define APREQ_XS_DEFINE_TABLE_NEXTKEY(attr) \
static XS(apreq_xs_##attr##_NEXTKEY) \
{ \
dXSARGS; \
SV *sv, *obj; \
IV idx; \
apr_table_t *t; \
const apr_array_header_t *arr; \
apr_table_entry_t *te; \
\
if (!SvROK(ST(0))) \
Perl_croak(aTHX_ "Usage: $table->NEXTKEY($prev)"); \
obj = apreq_xs_find_obj(aTHX_ ST(0), #attr); \
t = apreq_xs_##attr##_sv2table(obj); \
arr = apr_table_elts(t); \
te = (apr_table_entry_t *)arr->elts; \
\
if (items == 1) \
SvCUR(obj) = 0; \
\
if (SvCUR(obj) >= arr->nelts) { \
SvCUR(obj) = 0; \
XSRETURN_UNDEF; \
} \
idx = SvCUR(obj)++; \
sv = newSVpv(te[idx].key, 0); \
if (SvTAINTED(obj)) \
SvTAINTED_on(sv); \
ST(0) = sv_2mortal(sv); \
XSRETURN(1); \
}
#define APREQ_XS_DEFINE_TABLE_DO(attr,type,subclass) \
static int apreq_xs_##attr##_do_sub(void *data, const char *key, \
const char *val) \
{ \
struct apreq_xs_do_arg *d = data; \
apreq_##type##_t *RETVAL = apreq_value_to_##type( \
apreq_strtoval(val)); \
dTHXa(d->perl); \
dSP; \
SV *sv; \
void *env; \
int rv; \
\
env = d->env; \
\
ENTER; \
SAVETMPS; \
\
PUSHMARK(SP); \
EXTEND(SP,2); \
\
sv = newSVpv(key, 0); \
if (d->tainted) \
SvTAINTED_on(sv); \
PUSHs(sv_2mortal(sv)); \
\
sv = apreq_xs_##type##2sv(RETVAL, d->pkg, d->parent); \
if (d->tainted) \
SvTAINTED_on(sv); \
PUSHs(sv_2mortal(sv)); \
\
PUTBACK; \
rv = call_sv(d->sub, G_SCALAR); \
SPAGAIN; \
rv = (1 == rv) ? POPi : 1; \
PUTBACK; \
FREETMPS; \
LEAVE; \
\
return rv; \
} \
\
static XS(apreq_xs_##attr##_do) \
{ \
dXSARGS; \
struct apreq_xs_do_arg d = { NULL, NULL, NULL, NULL, 0, aTHX }; \
apr_table_t *t; \
void *env; \
int i, rv = 1; \
SV *sv, *obj; \
MAGIC *mg; \
\
if (items < 2 || !SvROK(ST(0)) || !SvROK(ST(1))) \
Perl_croak(aTHX_ "Usage: $object->do(\\&callback, @keys)"); \
sv = ST(0); \
obj = apreq_xs_find_obj(aTHX_ sv, #attr); \
env = apreq_xs_##attr##_sv2env(obj); \
t = apreq_xs_##attr##_sv2table(obj); \
mg = mg_find(obj, PERL_MAGIC_ext); \
d.parent = mg->mg_obj; \
d.pkg = mg->mg_len > 0 ? mg->mg_ptr : subclass; \
d.env = env; \
d.sub = ST(1); \
d.tainted = SvTAINTED(obj); \
if (items == 2) { \
rv = apr_table_do(apreq_xs_##attr##_do_sub, &d, t, NULL); \
XSRETURN_IV(rv); \
} \
\
for (i = 2; i < items; ++i) { \
const char *key = SvPV_nolen(ST(i)); \
rv = apr_table_do(apreq_xs_##attr##_do_sub, &d, t, key, NULL); \
if (rv == 0) \
break; \
} \
XSRETURN_IV(rv); \
}
#endif /* APREQ_XS_TABLES_H */