| /* Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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. |
| */ |
| |
| #include "mod_perl.h" |
| |
| modperl_handler_t *modperl_handler_new(apr_pool_t *p, const char *name) |
| { |
| modperl_handler_t *handler = |
| (modperl_handler_t *)apr_pcalloc(p, sizeof(*handler)); |
| |
| switch (*name) { |
| case '+': |
| ++name; |
| MpHandlerAUTOLOAD_On(handler); |
| break; |
| case '-': |
| ++name; |
| /* XXX: currently a noop; should disable autoload of given handler |
| * if PerlOptions +AutoLoad is configured |
| * see: modperl_hash_handlers in modperl_mgv.c |
| */ |
| MpHandlerAUTOLOAD_Off(handler); |
| break; |
| } |
| |
| /* not necessary due to apr_pcalloc */ |
| /* handler->cv = NULL; */ |
| handler->name = name; |
| MP_TRACE_h(MP_FUNC, "new handler %s", handler->name); |
| |
| return handler; |
| } |
| |
| /* How anon-subs are handled: |
| * We have two ways anon-subs can be registered |
| * A) at startup from httpd.conf: |
| * PerlTransHandler 'sub { ... }' |
| * B) run-time perl code |
| * $r->push_handlers(PerlTransHandler => sub { .... }); |
| * $s->push_handlers(PerlTransHandler => sub { .... }); |
| * |
| * In the case of non-threaded perl, we just compile A or grab B and |
| * store it in the mod_perl struct and call it when it's used. No |
| * problems here |
| * |
| * In the case of threads, things get more complicated. we no longer |
| * can store the CV value of the compiled anon-sub, since when |
| * perl_clone is called each interpreter will have a different CV |
| * value. since we need to be able to have 1 entry for each anon-sub |
| * across all interpreters a different solution is needed. to remind |
| * in the case of named subs, we just store the name of the sub and |
| * look its corresponding CV when we need it. |
| * |
| * The used solution: each process has a global counter, which always |
| * grows. Every time a new anon-sub is encountered, a new ID is |
| * allocated from that process-global counter and that ID is stored in |
| * the mod_perl struct. The compiled CV is stored as |
| * $PL_modglobal{ANONSUB}{$id} = CV; |
| * when perl_clone is called, each clone will clone that CV value, but |
| * we will still be able to find it, since we stored it in the |
| * hash. so we retrieve the CV value, whatever it is and we run it. |
| * |
| * that explanation can be written and run in perl: |
| * |
| * use threads; |
| * our %h; |
| * $h{x} = eval 'sub { print qq[this is sub @_\n] }'; |
| * $h{x}->("main"); |
| * threads->new(sub { $h{x}->(threads->self->tid)}); |
| * |
| * XXX: more nuances will follow |
| */ |
| |
| void modperl_handler_anon_init(pTHX_ apr_pool_t *p) |
| { |
| modperl_modglobal_key_t *gkey = |
| modperl_modglobal_lookup(aTHX_ "ANONSUB"); |
| MP_TRACE_h(MP_FUNC, "init $PL_modglobal{ANONSUB} = []"); |
| (void)MP_MODGLOBAL_STORE_HV(gkey); |
| } |
| |
| /* allocate and populate the anon handler sub-struct */ |
| MP_INLINE modperl_mgv_t *modperl_handler_anon_next(pTHX_ apr_pool_t *p) |
| { |
| /* re-use modperl_mgv_t entry which is otherwise is not used |
| * by anon handlers */ |
| modperl_mgv_t *anon = |
| (modperl_mgv_t *)apr_pcalloc(p, sizeof(*anon)); |
| |
| anon->name = apr_psprintf(p, "anon%d", modperl_global_anon_cnt_next()); |
| anon->len = strlen(anon->name); |
| PERL_HASH(anon->hash, anon->name, anon->len); |
| |
| MP_TRACE_h(MP_FUNC, "new anon handler: '%s'", anon->name); |
| return anon; |
| } |
| |
| MP_INLINE void modperl_handler_anon_add(pTHX_ modperl_mgv_t *anon, CV *cv) |
| { |
| modperl_modglobal_key_t *gkey = |
| modperl_modglobal_lookup(aTHX_ "ANONSUB"); |
| HE *he = MP_MODGLOBAL_FETCH(gkey); |
| HV *hv; |
| |
| if (!(he && (hv = (HV*)HeVAL(he)))) { |
| Perl_croak(aTHX_ "modperl_handler_anon_add: " |
| "can't find ANONSUB top entry (get)"); |
| } |
| |
| SvREFCNT_inc(cv); |
| if (!(*hv_store(hv, anon->name, anon->len, (SV*)cv, anon->hash))) { |
| SvREFCNT_dec(cv); |
| Perl_croak(aTHX_ "hv_store of anonsub '%s' has failed!", anon->name); |
| } |
| |
| MP_TRACE_h(MP_FUNC, "anonsub '%s' added", anon->name); |
| } |
| |
| MP_INLINE CV *modperl_handler_anon_get(pTHX_ modperl_mgv_t *anon) |
| { |
| modperl_modglobal_key_t *gkey = |
| modperl_modglobal_lookup(aTHX_ "ANONSUB"); |
| HE *he = MP_MODGLOBAL_FETCH(gkey); |
| HV *hv; |
| SV *sv; |
| |
| if (!(he && (hv = (HV*)HeVAL(he)))) { |
| Perl_croak(aTHX_ "modperl_handler_anon_get: " |
| "can't find ANONSUB top entry (get)"); |
| } |
| |
| if ((he = hv_fetch_he(hv, anon->name, anon->len, anon->hash))) { |
| sv = HeVAL(he); |
| MP_TRACE_h(MP_FUNC, "anonsub gets name '%s'", anon->name); |
| } |
| else { |
| Perl_croak(aTHX_ "can't find ANONSUB's '%s' entry", anon->name); |
| } |
| |
| return (CV*)sv; |
| } |
| |
| static |
| modperl_handler_t *modperl_handler_new_anon(pTHX_ apr_pool_t *p, CV *cv) |
| { |
| modperl_handler_t *handler = |
| (modperl_handler_t *)apr_pcalloc(p, sizeof(*handler)); |
| MpHandlerPARSED_On(handler); |
| MpHandlerANON_On(handler); |
| |
| #ifdef USE_ITHREADS |
| handler->cv = NULL; |
| handler->name = NULL; |
| handler->mgv_obj = modperl_handler_anon_next(aTHX_ p); |
| modperl_handler_anon_add(aTHX_ handler->mgv_obj, cv); |
| #else |
| /* it's safe to cache and later use the cv, since the same perl |
| * interpeter is always used */ |
| SvREFCNT_inc((SV*)cv); |
| handler->cv = cv; |
| handler->name = NULL; |
| |
| MP_TRACE_h(MP_FUNC, "new cached cv anon handler"); |
| #endif |
| |
| return handler; |
| } |
| |
| MP_INLINE |
| const char *modperl_handler_name(modperl_handler_t *handler) |
| { |
| /* a handler containing an anonymous sub doesn't have a normal sub |
| * name */ |
| if (handler->name) { |
| return handler->name; |
| } |
| else { |
| /* anon sub stores the internal modperl name in mgv_obj */ |
| return handler->mgv_obj ? handler->mgv_obj->name : "anonsub"; |
| } |
| } |
| |
| |
| int modperl_handler_resolve(pTHX_ modperl_handler_t **handp, |
| apr_pool_t *p, server_rec *s) |
| { |
| int duped=0; |
| modperl_handler_t *handler = *handp; |
| |
| #ifdef USE_ITHREADS |
| if (modperl_threaded_mpm() && p && |
| !MpHandlerPARSED(handler) && !MpHandlerDYNAMIC(handler)) { |
| /* |
| * under threaded mpm we cannot update the handler structure |
| * at request time without locking, so just copy it |
| */ |
| handler = *handp = modperl_handler_dup(p, handler); |
| duped = 1; |
| } |
| #endif |
| |
| MP_TRACE_h_do(MpHandler_dump_flags(handler, |
| modperl_handler_name(handler))); |
| |
| if (!MpHandlerPARSED(handler)) { |
| apr_pool_t *rp = duped ? p : s->process->pconf; |
| MpHandlerAUTOLOAD_On(handler); |
| |
| MP_TRACE_h(MP_FUNC, |
| "[%s] handler %s hasn't yet been resolved, " |
| "attempting to resolve using %s pool 0x%lx", |
| modperl_server_desc(s, p), |
| modperl_handler_name(handler), |
| duped ? "current" : "server conf", |
| (unsigned long)rp); |
| |
| if (!modperl_mgv_resolve(aTHX_ handler, rp, handler->name, FALSE)) { |
| modperl_errsv_prepend(aTHX_ |
| "failed to resolve handler `%s': ", |
| handler->name); |
| return HTTP_INTERNAL_SERVER_ERROR; |
| } |
| } |
| |
| return OK; |
| } |
| |
| modperl_handler_t *modperl_handler_dup(apr_pool_t *p, |
| modperl_handler_t *h) |
| { |
| MP_TRACE_h(MP_FUNC, "dup handler %s", modperl_handler_name(h)); |
| return modperl_handler_new(p, h->name); |
| } |
| |
| int modperl_handler_equal(modperl_handler_t *h1, modperl_handler_t *h2) |
| { |
| if (h1->mgv_cv && h2->mgv_cv) { |
| return modperl_mgv_equal(h1->mgv_cv, h2->mgv_cv); |
| } |
| return strEQ(h1->name, h2->name); |
| } |
| |
| MpAV *modperl_handler_array_merge(apr_pool_t *p, MpAV *base_a, MpAV *add_a) |
| { |
| int i, j; |
| modperl_handler_t **base_h, **add_h; |
| MpAV *mrg_a; |
| |
| if (!add_a) { |
| return base_a; |
| } |
| |
| if (!base_a) { |
| return add_a; |
| } |
| |
| mrg_a = apr_array_copy(p, base_a); |
| |
| base_h = (modperl_handler_t **)base_a->elts; |
| add_h = (modperl_handler_t **)add_a->elts; |
| |
| for (i=0; i<base_a->nelts; i++) { |
| for (j=0; j<add_a->nelts; j++) { |
| if (modperl_handler_equal(base_h[i], add_h[j])) { |
| MP_TRACE_d(MP_FUNC, "both base and new config contain %s", |
| add_h[j]->name); |
| } |
| else { |
| modperl_handler_array_push(mrg_a, add_h[j]); |
| MP_TRACE_d(MP_FUNC, "base does not contain %s", |
| add_h[j]->name); |
| } |
| } |
| } |
| |
| return mrg_a; |
| } |
| |
| void modperl_handler_make_args(pTHX_ AV **avp, ...) |
| { |
| va_list args; |
| |
| if (!*avp) { |
| *avp = newAV(); /* XXX: cache an intialized AV* per-request */ |
| } |
| |
| va_start(args, avp); |
| |
| for (;;) { |
| char *classname = va_arg(args, char *); |
| void *ptr; |
| SV *sv; |
| |
| if (classname == NULL) { |
| break; |
| } |
| |
| ptr = va_arg(args, void *); |
| |
| switch (*classname) { |
| case 'A': |
| if (strEQ(classname, "APR::Table")) { |
| sv = modperl_hash_tie(aTHX_ classname, (SV *)NULL, ptr); |
| break; |
| } |
| case 'I': |
| if (strEQ(classname, "IV")) { |
| sv = ptr ? newSViv(PTR2IV(ptr)) : &PL_sv_undef; |
| break; |
| } |
| case 'P': |
| if (strEQ(classname, "PV")) { |
| sv = ptr ? newSVpv((char *)ptr, 0) : &PL_sv_undef; |
| break; |
| } |
| case 'H': |
| if (strEQ(classname, "HV")) { |
| sv = newRV_noinc((SV*)ptr); |
| break; |
| } |
| default: |
| sv = modperl_ptr2obj(aTHX_ classname, ptr); |
| break; |
| } |
| |
| av_push(*avp, sv); |
| } |
| |
| va_end(args); |
| } |
| |
| #define set_desc(dtype) \ |
| if (desc) *desc = modperl_handler_desc_##dtype(idx) |
| |
| /* We should be able to use PERL_GET_CONTEXT here. The rcfg condition |
| * makes sure there is a request being processed. The action > GET part |
| * means it is a $r->set_handlers or $r->push_handlers operation. This |
| * can only happen if called by perl code. |
| */ |
| #define check_modify(dtype) \ |
| if ((action > MP_HANDLER_ACTION_GET) && rcfg) { \ |
| dTHXa(PERL_GET_CONTEXT); \ |
| MP_ASSERT(aTHX+0); \ |
| Perl_croak(aTHX_ "too late to modify %s handlers", \ |
| modperl_handler_desc_##dtype(idx)); \ |
| } |
| |
| /* |
| * generic function to lookup handlers for use in modperl_callback(), |
| * $r->{push,set,get}_handlers, $s->{push,set,get}_handlers |
| * $s->push/set at startup time are the same as configuring Perl*Handlers |
| * $r->push/set at request time will create entries in r->request_config |
| * push will first merge with configured handlers, unless an entry |
| * in r->request_config already exists. in this case, push or set has |
| * already been called for the given handler, |
| * r->request_config entries then override those in r->per_dir_config |
| */ |
| |
| MpAV **modperl_handler_lookup_handlers(modperl_config_dir_t *dcfg, |
| modperl_config_srv_t *scfg, |
| modperl_config_req_t *rcfg, |
| apr_pool_t *p, |
| int type, int idx, |
| modperl_handler_action_e action, |
| const char **desc) |
| { |
| MpAV **avp = NULL, **ravp = NULL; |
| |
| switch (type) { |
| case MP_HANDLER_TYPE_PER_DIR: |
| avp = &dcfg->handlers_per_dir[idx]; |
| if (rcfg) { |
| ravp = &rcfg->handlers_per_dir[idx]; |
| } |
| set_desc(per_dir); |
| break; |
| case MP_HANDLER_TYPE_PER_SRV: |
| avp = &scfg->handlers_per_srv[idx]; |
| if (rcfg) { |
| ravp = &rcfg->handlers_per_srv[idx]; |
| } |
| set_desc(per_srv); |
| break; |
| case MP_HANDLER_TYPE_PRE_CONNECTION: |
| avp = &scfg->handlers_pre_connection[idx]; |
| check_modify(pre_connection); |
| set_desc(pre_connection); |
| break; |
| case MP_HANDLER_TYPE_CONNECTION: |
| avp = &scfg->handlers_connection[idx]; |
| check_modify(connection); |
| set_desc(connection); |
| break; |
| case MP_HANDLER_TYPE_FILES: |
| avp = &scfg->handlers_files[idx]; |
| check_modify(files); |
| set_desc(files); |
| break; |
| case MP_HANDLER_TYPE_PROCESS: |
| avp = &scfg->handlers_process[idx]; |
| check_modify(files); |
| set_desc(process); |
| break; |
| }; |
| |
| if (!avp) { |
| /* should never happen */ |
| #if 0 |
| fprintf(stderr, "PANIC: no such handler type: %d\n", type); |
| #endif |
| return NULL; |
| } |
| |
| switch (action) { |
| case MP_HANDLER_ACTION_GET: |
| /* just a lookup */ |
| break; |
| case MP_HANDLER_ACTION_PUSH: |
| if (ravp) { |
| if (!*ravp) { |
| if (*avp) { |
| /* merge with existing configured handlers */ |
| *ravp = apr_array_copy(p, *avp); |
| } |
| else { |
| /* no request handlers have been previously pushed or set */ |
| *ravp = modperl_handler_array_new(p); |
| } |
| } |
| } |
| else if (!*avp) { |
| /* directly modify the configuration at startup time */ |
| *avp = modperl_handler_array_new(p); |
| } |
| break; |
| case MP_HANDLER_ACTION_SET: |
| if (ravp) { |
| if (*ravp) { |
| /* wipe out existing pushed/set request handlers */ |
| (*ravp)->nelts = 0; |
| } |
| else { |
| /* no request handlers have been previously pushed or set */ |
| *ravp = modperl_handler_array_new(p); |
| } |
| } |
| else if (*avp) { |
| /* wipe out existing configuration, only at startup time */ |
| (*avp)->nelts = 0; |
| } |
| else { |
| /* no configured handlers for this phase */ |
| *avp = modperl_handler_array_new(p); |
| } |
| break; |
| } |
| |
| return (ravp && *ravp) ? ravp : avp; |
| } |
| |
| MpAV **modperl_handler_get_handlers(request_rec *r, conn_rec *c, server_rec *s, |
| apr_pool_t *p, const char *name, |
| modperl_handler_action_e action) |
| { |
| MP_dSCFG(s); |
| MP_dDCFG; |
| MP_dRCFG; |
| |
| int idx, type; |
| |
| if (!r) { |
| /* so $s->{push,set}_handlers can configured request-time handlers */ |
| dcfg = modperl_config_dir_get_defaults(s); |
| } |
| |
| if ((idx = modperl_handler_lookup(name, &type)) == DECLINED) { |
| return FALSE; |
| } |
| |
| return modperl_handler_lookup_handlers(dcfg, scfg, rcfg, p, |
| type, idx, |
| action, NULL); |
| } |
| |
| modperl_handler_t *modperl_handler_new_from_sv(pTHX_ apr_pool_t *p, SV *sv) |
| { |
| char *name = NULL; |
| GV *gv; |
| |
| if (SvROK(sv)) { |
| sv = SvRV(sv); |
| } |
| |
| switch (SvTYPE(sv)) { |
| case SVt_PV: |
| name = SvPVX(sv); |
| return modperl_handler_new(p, apr_pstrdup(p, name)); |
| break; |
| case SVt_PVCV: |
| if (CvANON((CV*)sv)) { |
| return modperl_handler_new_anon(aTHX_ p, (CV*)sv); |
| } |
| if (!(gv = CvGV((CV*)sv))) { |
| Perl_croak(aTHX_ "can't resolve the code reference"); |
| } |
| name = apr_pstrcat(p, HvNAME(GvSTASH(gv)), "::", GvNAME(gv), NULL); |
| return modperl_handler_new(p, name); |
| default: |
| break; |
| }; |
| |
| return NULL; |
| } |
| |
| int modperl_handler_push_handlers(pTHX_ apr_pool_t *p, |
| MpAV *handlers, SV *sv) |
| { |
| modperl_handler_t *handler = modperl_handler_new_from_sv(aTHX_ p, sv); |
| |
| if (handler) { |
| modperl_handler_array_push(handlers, handler); |
| return TRUE; |
| } |
| |
| MP_TRACE_h(MP_FUNC, "unable to push_handler 0x%lx", |
| (unsigned long)sv); |
| |
| return FALSE; |
| } |
| |
| /* convert array header of modperl_handlers_t's to AV ref of CV refs */ |
| SV *modperl_handler_perl_get_handlers(pTHX_ MpAV **handp, apr_pool_t *p) |
| { |
| AV *av = newAV(); |
| int i; |
| modperl_handler_t **handlers; |
| |
| if (!(handp && *handp)) { |
| return &PL_sv_undef; |
| } |
| |
| av_extend(av, (*handp)->nelts - 1); |
| |
| handlers = (modperl_handler_t **)(*handp)->elts; |
| |
| for (i=0; i<(*handp)->nelts; i++) { |
| modperl_handler_t *handler = NULL; |
| GV *gv; |
| |
| if (MpHandlerPARSED(handlers[i])) { |
| handler = handlers[i]; |
| } |
| else { |
| #ifdef USE_ITHREADS |
| if (!MpHandlerDYNAMIC(handlers[i])) { |
| handler = modperl_handler_dup(p, handlers[i]); |
| } |
| #endif |
| if (!handler) { |
| handler = handlers[i]; |
| } |
| |
| if (!modperl_mgv_resolve(aTHX_ handler, p, handler->name, TRUE)) { |
| MP_TRACE_h(MP_FUNC, "failed to resolve handler %s", |
| handler->name); |
| } |
| |
| } |
| |
| if (handler->mgv_cv) { |
| if ((gv = modperl_mgv_lookup(aTHX_ handler->mgv_cv))) { |
| CV *cv = modperl_mgv_cv(gv); |
| av_push(av, newRV_inc((SV*)cv)); |
| } |
| } |
| else { |
| av_push(av, newSVpv(handler->name, 0)); |
| } |
| } |
| |
| return newRV_noinc((SV*)av); |
| } |
| |
| #define push_sv_handler \ |
| if ((modperl_handler_push_handlers(aTHX_ p, *handlers, sv))) { \ |
| MpHandlerDYNAMIC_On(modperl_handler_array_last(*handlers)); \ |
| } |
| |
| /* allow push/set of single cv ref or array ref of cv refs */ |
| int modperl_handler_perl_add_handlers(pTHX_ |
| request_rec *r, |
| conn_rec *c, |
| server_rec *s, |
| apr_pool_t *p, |
| const char *name, |
| SV *sv, |
| modperl_handler_action_e action) |
| { |
| I32 i; |
| AV *av = (AV *)NULL; |
| MpAV **handlers = |
| modperl_handler_get_handlers(r, c, s, |
| p, name, action); |
| |
| if (!(handlers && *handlers)) { |
| return FALSE; |
| } |
| |
| if (SvROK(sv) && (SvTYPE(SvRV(sv)) == SVt_PVAV)) { |
| av = (AV*)SvRV(sv); |
| |
| for (i=0; i <= AvFILL(av); i++) { |
| sv = *av_fetch(av, i, FALSE); |
| push_sv_handler; |
| } |
| } |
| else { |
| push_sv_handler; |
| } |
| |
| return TRUE; |
| } |
| |
| /* |
| * Local Variables: |
| * c-basic-offset: 4 |
| * indent-tabs-mode: nil |
| * End: |
| */ |