/* ==================================================================== * The Apache Software License, Version 1.1 * * Copyright (c) 1996-2000 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, * if any, must include the following acknowledgment: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "Apache" and "Apache Software Foundation" must * not be used to endorse or promote products derived from this * software without prior written permission. For written * permission, please contact apache@apache.org. * * 5. Products derived from this software may not be called "Apache", * nor may "Apache" appear in their name, without prior written * permission of the Apache Software Foundation. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== */ /* * And so it was decided the camel should be given magical multi-colored * feathers so it could fly and journey to once unknown worlds. * And so it was done... */ #define CORE_PRIVATE #include "mod_perl.h" #ifdef WIN32 void *mod_perl_mutex = &mod_perl_mutex; #else void *mod_perl_dummy_mutex = &mod_perl_dummy_mutex; #endif static IV mp_request_rec; static int seqno = 0; static int perl_is_running = 0; int mod_perl_socketexitoption = 3; int mod_perl_weareaforkedchild = 0; static int callbacks_this_request = 0; static PerlInterpreter *perl = NULL; static AV *orig_inc = Nullav; static AV *cleanup_av = Nullav; #ifdef PERL_STACKED_HANDLERS static HV *stacked_handlers = Nullhv; #endif #ifdef PERL_OBJECT CPerlObj *pPerl; #endif typedef const char* (*crft)(); /* command_req_func_t */ static command_rec perl_cmds[] = { #ifdef PERL_SECTIONS { "", (crft) perl_section, NULL, SECTION_ALLOWED, RAW_ARGS, "Perl code" }, { "", (crft) perl_end_section, NULL, SECTION_ALLOWED, NO_ARGS, "End Perl code" }, #endif { "=pod", (crft) perl_pod_section, NULL, OR_ALL, RAW_ARGS, "Start of POD" }, { "=back", (crft) perl_pod_section, NULL, OR_ALL, RAW_ARGS, "End of =over" }, { "=cut", (crft) perl_pod_end_section, NULL, OR_ALL, NO_ARGS, "End of POD" }, { "__END__", (crft) perl_config_END, NULL, OR_ALL, RAW_ARGS, "Stop reading config" }, { "PerlFreshRestart", (crft) perl_cmd_fresh_restart, NULL, RSRC_CONF, FLAG, "Tell mod_perl to reload modules and flush Apache::Registry cache on restart" }, { "PerlTaintCheck", (crft) perl_cmd_tainting, NULL, RSRC_CONF, FLAG, "Turn on -T switch" }, #ifdef PERL_SAFE_STARTUP { "PerlOpmask", (crft) perl_cmd_opmask, NULL, RSRC_CONF, TAKE1, "Opmask File" }, #endif { "PerlWarn", (crft) perl_cmd_warn, NULL, RSRC_CONF, FLAG, "Turn on -w switch" }, { "PerlScript", (crft) perl_cmd_require, NULL, OR_ALL, ITERATE, "this directive is deprecated, use `PerlRequire'" }, { "PerlRequire", (crft) perl_cmd_require, NULL, OR_ALL, ITERATE, "A Perl script name, pulled in via require" }, { "PerlModule", (crft) perl_cmd_module, NULL, OR_ALL, ITERATE, "List of Perl modules" }, { "PerlSetVar", (crft) perl_cmd_var, NULL, OR_ALL, TAKE2, "Perl config var and value" }, { "PerlAddVar", (crft) perl_cmd_var, (void*)1, OR_ALL, ITERATE2, "Perl config var and value" }, { "PerlSetEnv", (crft) perl_cmd_setenv, NULL, OR_ALL, TAKE2, "Perl %ENV key and value" }, { "PerlPassEnv", (crft) perl_cmd_pass_env, NULL, RSRC_CONF, ITERATE, "pass environment variables to %ENV"}, { "PerlSendHeader", (crft) perl_cmd_sendheader, NULL, OR_ALL, FLAG, "Tell mod_perl to parse and send HTTP headers" }, { "PerlSetupEnv", (crft) perl_cmd_env, NULL, OR_ALL, FLAG, "Tell mod_perl to setup %ENV by default" }, { "PerlHandler", (crft) perl_cmd_handler_handlers, NULL, OR_ALL, ITERATE, "the Perl handler routine name" }, #ifdef PERL_TRANS { PERL_TRANS_CMD_ENTRY }, #endif #ifdef PERL_AUTHEN { PERL_AUTHEN_CMD_ENTRY }, #endif #ifdef PERL_AUTHZ { PERL_AUTHZ_CMD_ENTRY }, #endif #ifdef PERL_ACCESS { PERL_ACCESS_CMD_ENTRY }, #endif #ifdef PERL_TYPE { PERL_TYPE_CMD_ENTRY }, #endif #ifdef PERL_FIXUP { PERL_FIXUP_CMD_ENTRY }, #endif #ifdef PERL_LOG { PERL_LOG_CMD_ENTRY }, #endif #ifdef PERL_CLEANUP { PERL_CLEANUP_CMD_ENTRY }, #endif #ifdef PERL_INIT { PERL_INIT_CMD_ENTRY }, #endif #ifdef PERL_HEADER_PARSER { PERL_HEADER_PARSER_CMD_ENTRY }, #endif #ifdef PERL_CHILD_INIT { PERL_CHILD_INIT_CMD_ENTRY }, #endif #ifdef PERL_CHILD_EXIT { PERL_CHILD_EXIT_CMD_ENTRY }, #endif #ifdef PERL_POST_READ_REQUEST { PERL_POST_READ_REQUEST_CMD_ENTRY }, #endif #ifdef PERL_DISPATCH { PERL_DISPATCH_CMD_ENTRY }, #endif #ifdef PERL_RESTART { PERL_RESTART_CMD_ENTRY }, #endif { NULL } }; static handler_rec perl_handlers [] = { { "perl-script", perl_handler }, { DIR_MAGIC_TYPE, perl_handler }, { NULL } }; module MODULE_VAR_EXPORT perl_module = { STANDARD_MODULE_STUFF, perl_module_init, /* initializer */ perl_create_dir_config, /* create per-directory config structure */ perl_merge_dir_config, /* merge per-directory config structures */ perl_create_server_config, /* create per-server config structure */ perl_merge_server_config, /* merge per-server config structures */ perl_cmds, /* command table */ perl_handlers, /* handlers */ PERL_TRANS_HOOK, /* translate_handler */ PERL_AUTHEN_HOOK, /* check_user_id */ PERL_AUTHZ_HOOK, /* check auth */ PERL_ACCESS_HOOK, /* check access */ PERL_TYPE_HOOK, /* type_checker */ PERL_FIXUP_HOOK, /* pre-run fixups */ PERL_LOG_HOOK, /* logger */ #if MODULE_MAGIC_NUMBER >= 19970103 PERL_HEADER_PARSER_HOOK, /* header parser */ #endif #if MODULE_MAGIC_NUMBER >= 19970719 PERL_CHILD_INIT_HOOK, /* child_init */ #endif #if MODULE_MAGIC_NUMBER >= 19970728 NULL, /* child_exit *//* mod_perl uses register_cleanup() */ #endif #if MODULE_MAGIC_NUMBER >= 19970825 PERL_POST_READ_REQUEST_HOOK, /* post_read_request */ #endif }; #if defined(STRONGHOLD) && !defined(APACHE_SSL) #define APACHE_SSL #endif int PERL_RUNNING (void) { return (perl_is_running); } static void seqno_check_max(request_rec *r, int seqno) { dPPDIR; char *max = NULL; array_header *vars = (array_header *)cld->vars; /* XXX: what triggers such a condition ?*/ if(vars && (vars->nelts > 100000)) { fprintf(stderr, "[warning] PerlSetVar->nelts = %d\n", vars->nelts); } else { if(cld->vars) max = (char *)table_get(cld->vars, "MaxModPerlRequestsPerChild"); } #if (MODULE_MAGIC_NUMBER >= 19970912) && !defined(WIN32) if(max && (seqno >= atoi(max))) { child_terminate(r); MP_TRACE_g(fprintf(stderr, "mod_perl: terminating child %d after serving %d requests\n", (int)getpid(), seqno)); } #endif max = NULL; } void perl_shutdown (server_rec *s, pool *p) { char *pdl = NULL; if((pdl = getenv("PERL_DESTRUCT_LEVEL"))) perl_destruct_level = atoi(pdl); if(perl_destruct_level < 0) { MP_TRACE_g(fprintf(stderr, "skipping destruction of Perl interpreter\n")); return; } /* execute END blocks we suspended during perl_startup() */ perl_run_endav("perl_shutdown"); MP_TRACE_g(fprintf(stderr, "destructing and freeing Perl interpreter (level=%d)...", perl_destruct_level)); perl_util_cleanup(); mp_request_rec = 0; av_undef(orig_inc); SvREFCNT_dec((SV*)orig_inc); orig_inc = Nullav; av_undef(cleanup_av); SvREFCNT_dec((SV*)cleanup_av); cleanup_av = Nullav; #ifdef PERL_STACKED_HANDLERS hv_undef(stacked_handlers); SvREFCNT_dec((SV*)stacked_handlers); stacked_handlers = Nullhv; #endif perl_destruct(perl); perl_free(perl); #ifdef USE_THREADS PERL_SYS_TERM(); #endif perl_is_running = 0; MP_TRACE_g(fprintf(stderr, "ok\n")); } request_rec *mp_fake_request_rec(server_rec *s, pool *p, char *hook) { request_rec *r = (request_rec *)pcalloc(p, sizeof(request_rec)); r->pool = p; r->server = s; r->per_dir_config = NULL; r->uri = hook; r->notes = NULL; return r; } #ifdef PERL_RESTART void perl_restart_handler(server_rec *s, pool *p) { char *hook = "PerlRestartHandler"; dSTATUS; dPSRV(s); request_rec *r = mp_fake_request_rec(s, p, hook); PERL_CALLBACK(hook, cls->PerlRestartHandler); } #endif void perl_restart(server_rec *s, pool *p) { /* restart as best we can */ SV *rgy_cache = perl_get_sv("Apache::Registry", FALSE); HV *rgy_symtab = (HV*)gv_stashpv("Apache::ROOT", FALSE); ENTER; SAVESPTR(warnhook); warnhook = perl_eval_pv("sub {}", TRUE); /* the file-stat cache */ if(rgy_cache) sv_setsv(rgy_cache, &sv_undef); /* the symbol table we compile registry scripts into */ if(rgy_symtab) hv_clear(rgy_symtab); if(endav) { SvREFCNT_dec(endav); endav = Nullav; } #ifdef STACKED_HANDLERS if(stacked_handlers) hv_clear(stacked_handlers); #endif /* reload %INC */ perl_reload_inc(s, p); LEAVE; /*mod_perl_notice(s, "mod_perl restarted"); */ MP_TRACE_g(fprintf(stderr, "perl_restart: ok\n")); } U32 mp_debug = 0; static void mod_perl_set_cwd(void) { char *name = "Apache::Server::CWD"; GV *gv = gv_fetchpv(name, GV_ADDMULTI, SVt_PV); char *pwd = getenv("PWD"); if(pwd) sv_setpv(GvSV(gv), pwd); else sv_setsv(GvSV(gv), perl_eval_pv("require Cwd; Cwd::getcwd()", TRUE)); mod_perl_untaint(GvSV(gv)); } #ifdef PERL_TIE_SCRIPTNAME static PERL_MG_UFUNC(scriptname_val, ix, sv) { dTHR; request_rec *r = perl_request_rec(NULL); if(r) sv_setpv(sv, r->filename); else if(strNE(SvPVX(GvSV(CopFILEGV(curcop))), "-e")) sv_setsv(sv, GvSV(CopFILEGV(curcop))); else { SV *file = perl_eval_pv("(caller())[1]",TRUE); sv_setsv(sv, file); } MP_TRACE_g(fprintf(stderr, "FETCH $0 => %s\n", SvPV(sv,na))); return TRUE; } static void mod_perl_tie_scriptname(void) { SV *sv = perl_get_sv("0",TRUE); struct ufuncs umg; umg.uf_val = scriptname_val; umg.uf_set = NULL; umg.uf_index = (IV)0; sv_unmagic(sv, 'U'); sv_magic(sv, Nullsv, 'U', (char*) &umg, sizeof(umg)); } #else #define mod_perl_tie_scriptname() #endif #define saveINC \ if(orig_inc) SvREFCNT_dec(orig_inc); \ orig_inc = av_copy_array(GvAV(incgv)) #define dl_librefs "DynaLoader::dl_librefs" #define dl_modules "DynaLoader::dl_modules" static array_header *xs_dl_librefs(pool *p) { I32 i; AV *librefs = perl_get_av(dl_librefs, FALSE); AV *modules = perl_get_av(dl_modules, FALSE); array_header *arr; if (!librefs) { MP_TRACE_g(fprintf(stderr, "Could not get @%s for unloading.\n", dl_librefs)); return NULL; } arr = ap_make_array(p, AvFILL(librefs)-1, sizeof(void *)); for (i=0; i<=AvFILL(librefs); i++) { void *handle; SV *handle_sv = *av_fetch(librefs, i, FALSE); SV *module_sv = *av_fetch(modules, i, FALSE); if(!handle_sv) { MP_TRACE_g(fprintf(stderr, "Could not fetch $%s[%d]!\n", dl_librefs, (int)i)); continue; } handle = (void *)SvIV(handle_sv); MP_TRACE_g(fprintf(stderr, "%s dl handle == 0x%lx\n", SvPVX(module_sv), (unsigned long)handle)); if (handle) { *(void **)ap_push_array(arr) = handle; } } av_clear(modules); av_clear(librefs); return arr; } static void unload_xs_so(array_header *librefs) { int i; if (!librefs) { return; } for (i=0; i < librefs->nelts; i++) { void *handle = ((void **)librefs->elts)[i]; MP_TRACE_g(fprintf(stderr, "unload_xs_so: 0x%lx\n", (unsigned long)handle)); #ifdef _AIX /* make sure Perl's dlclose is used, instead of Apache's */ dlclose(handle); #else ap_os_dso_unload(handle); #endif } } static void mp_dso_unload(void *data) { array_header *librefs; librefs = xs_dl_librefs((pool *)data); perl_destruct_level = 2; perl_shutdown(NULL, NULL); unload_xs_so(librefs); } static void mp_server_notstarting(void *data) { saveINC; require_Apache(NULL); Apache__ServerStarting(FALSE); } #define Apache__ServerStarting_on() \ Apache__ServerStarting(PERL_RUNNING()); \ if(!PERL_IS_DSO) \ register_cleanup(p, NULL, mp_server_notstarting, mod_perl_noop) #define MP_APACHE_VERSION "1.27" void mp_check_version(void) { I32 i; SV *namesv; SV *version; STRLEN n_a; require_Apache(NULL); if(!(version = perl_get_sv("Apache::VERSION", FALSE))) { /* should never happen but might if the perl mod_perl * was built with isn't around anymore */ croak("Apache.pm failed to load! (%s)", SvTRUE(ERRSV) ? SvPV(ERRSV,na) : "no error?" ); } if(strEQ(SvPV(version,n_a), MP_APACHE_VERSION)) /*no worries*/ return; fprintf(stderr, "Apache.pm version %s required!\n", MP_APACHE_VERSION); fprintf(stderr, "%s", form("%_ is version %_\n", *hv_fetch(GvHV(incgv), "Apache.pm", 9, FALSE), version)); fprintf(stderr, "Perhaps you forgot to 'make install' or need to uninstall an old version?\n"); namesv = NEWSV(806, 0); for(i=0; i<=AvFILL(GvAV(incgv)); i++) { char *tryname; PerlIO *tryrsfp = 0; SV *dir = *av_fetch(GvAV(incgv), i, TRUE); sv_setpvf(namesv, "%_/Apache.pm", dir); tryname = SvPVX(namesv); if((tryrsfp = PerlIO_open(tryname, "r"))) { fprintf(stderr, "Found: %s\n", tryname); PerlIO_close(tryrsfp); } } SvREFCNT_dec(namesv); exit(1); } #if !HAS_MMN_136 static void set_sigpipe(void) { char *dargs[] = { NULL }; perl_require_module("Apache::SIG", NULL); perl_call_argv("Apache::SIG::set", G_DISCARD, dargs); } #endif void perl_module_init(server_rec *s, pool *p) { #if HAS_MMN_130 ap_add_version_component(MOD_PERL_STRING_VERSION); if(PERL_RUNNING()) { #ifdef PERL_IS_5_6 char *version = form("Perl/v%vd", PL_patchlevel); #else char *version = form("Perl/%_", perl_get_sv("]", TRUE)); #endif if(perl_get_sv("Apache::Server::AddPerlVersion", FALSE)) { ap_add_version_component(version); } } #endif perl_startup(s, p); } static void mod_perl_boot(void *data) { /* make sure DynaLoader is loaded before XSLoader * to workaround bug in 5.6.1 that can trigger a segv * when using modperl as a dso */ perl_require_module("DynaLoader", NULL); } static void mod_perl_xs_init(pTHX) { xs_init(aTHX); /* XXX: in 5.7.2+ we can call the body of mod_perl_boot here * but in 5.6.1 the Perl runtime is not properly setup yet * so we have to pull this stunt to delay */ #ifdef SAVEDESTRUCTOR_X SAVEDESTRUCTOR_X(mod_perl_boot, 0); #endif } void perl_startup (server_rec *s, pool *p) { char *argv[] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL }; char **entries, *dstr; int status, i, argc=1; dPSRV(s); SV *pool_rv, *server_rv; GV *gv, *shgv; #ifndef WIN32 argv[0] = server_argv0; #endif #ifdef PERL_TRACE if((dstr = getenv("MOD_PERL_TRACE"))) { if(strEQ(dstr, "all")) { mp_debug = 0xffffffff; } else if (isALPHA(dstr[0])) { static char debopts[] = "dshgc"; char *d; for (; *dstr && (d = strchr(debopts,*dstr)); dstr++) mp_debug |= 1 << (d - debopts); } else { mp_debug = atoi(dstr); } mp_debug |= 0x80000000; } #else dstr = NULL; #endif if(PERL_RUNNING()) { saveINC; mp_check_version(); #if !HAS_MMN_136 set_sigpipe(); #endif } if(perl_is_running == 0) { /* we'll boot Perl below */ } else if(perl_is_running < PERL_DONE_STARTUP) { /* skip the -HUP at server-startup */ perl_is_running++; Apache__ServerStarting_on(); MP_TRACE_g(fprintf(stderr, "perl_startup: perl aleady running...ok\n")); return; } else { Apache__ServerReStarting(TRUE); #ifdef PERL_RESTART perl_restart_handler(s, p); #endif if(cls->FreshRestart) perl_restart(s, p); Apache__ServerReStarting(FALSE); return; } perl_is_running++; /* fake-up what the shell usually gives perl */ if(cls->PerlTaintCheck) argv[argc++] = "-T"; if(cls->PerlWarn) argv[argc++] = "-w"; #ifdef WIN32 argv[argc++] = "nul"; #else argv[argc++] = "/dev/null"; #endif MP_TRACE_g(fprintf(stderr, "perl_parse args: ")); for(i=1; iPerlTaintCheck); (void)GvSV_init("Apache::__SendHeader"); (void)GvSV_init("Apache::__CurrentCallback"); Apache__ServerReStarting(FALSE); /* just for -w */ Apache__ServerStarting_on(); #ifdef PERL_STACKED_HANDLERS if(!stacked_handlers) { stacked_handlers = newHV(); shgv = GvHV_init("Apache::PerlStackedHandlers"); GvHV(shgv) = stacked_handlers; } #endif #ifdef MULTITHREAD mod_perl_mutex = create_mutex(NULL); #endif if ((status = perl_run(perl)) != OK) { MP_TRACE_g(fprintf(stderr,"not ok, status=%d\n", status)); perror("run"); exit(1); } MP_TRACE_g(fprintf(stderr, "ok\n")); /* Force the environment to be copied out of its original location above argv[]. This fixes a crash caused when a module called putenv() before any Perl modified the environment - environ would change to a new value, and the check in my_setenv() to duplicate the environment would fail, and then setting some environment value which had a previous value would cause perl to try to free() something from the original env. This crashed free(). */ my_setenv("MODPERL_ENV_FIXUP", "0"); my_setenv("MODPERL_ENV_FIXUP", NULL); { dTHR; TAINT_NOT; /* At this time all is safe */ } #ifdef MOD_PERL_PREFIX av_unshift(GvAV(incgv),1); av_store(GvAV(incgv), 0, newSVpv(MOD_PERL_PREFIX,0)); #endif #ifdef APACHE_PERL5LIB perl_inc_unshift(APACHE_PERL5LIB); #else av_push(GvAV(incgv), newSVpv(server_root_relative(p,""),0)); av_push(GvAV(incgv), newSVpv(server_root_relative(p,"lib/perl"),0)); #endif /* *CORE::GLOBAL::exit = \&Apache::exit */ if(gv_stashpv("CORE::GLOBAL", FALSE)) { GV *exitgp = gv_fetchpv("CORE::GLOBAL::exit", TRUE, SVt_PVCV); GvCV(exitgp) = perl_get_cv("Apache::exit", TRUE); GvIMPORTED_CV_on(exitgp); } ENTER_SAFE(s,p); MP_TRACE_g(mod_perl_dump_opmask()); entries = (char **)cls->PerlRequire->elts; for(i = 0; i < cls->PerlRequire->nelts; i++) { if(perl_load_startup_script(s, p, entries[i], TRUE) != OK) { fprintf(stderr, "Require of Perl file `%s' failed, exiting...\n", entries[i]); exit(1); } } entries = (char **)cls->PerlModule->elts; for(i = 0; i < cls->PerlModule->nelts; i++) { if(perl_require_module(entries[i], s) != OK) { fprintf(stderr, "Can't load Perl module `%s', exiting...\n", entries[i]); exit(1); } } LEAVE_SAFE; MP_TRACE_g(fprintf(stderr, "mod_perl: %d END blocks encountered during server startup\n", endav ? (int)AvFILL(endav)+1 : 0)); #if MODULE_MAGIC_NUMBER < 19970728 if(endav) MP_TRACE_g(fprintf(stderr, "mod_perl: cannot run END blocks encoutered at server startup without apache_1.3.0+\n")); #endif saveINC; if (PERL_IS_DSO) { register_cleanup(p, p, mp_dso_unload, null_cleanup); } } int mod_perl_sent_header(request_rec *r, int val) { dPPDIR; if (val == DONE) { val = r->assbackwards = 1; /* so apache does not send another header */ } if(val) MP_SENTHDR_on(cld); val = MP_SENTHDR(cld) ? 1 : 0; return MP_SENDHDR(cld) ? val : 1; } #ifndef perl_init_ids #define perl_init_ids mod_perl_init_ids() #endif int perl_handler(request_rec *r) { dSTATUS; dPPDIR; dPPREQ; dTHR; GV *gv; #ifdef USE_ITHREADS dTHX; if (!aTHX) { PERL_SET_CONTEXT(perl); } #endif (void)acquire_mutex(mod_perl_mutex); gv = gv_fetchpv("SIG", TRUE, SVt_PVHV); #if 0 /* force 'PerlSendHeader On' for sub-requests * e.g. Apache::Sandwich */ if(r->main != NULL) MP_SENDHDR_on(cld); #endif if(MP_SENDHDR(cld)) MP_SENTHDR_off(cld); (void)perl_request_rec(r); MP_TRACE_g(fprintf(stderr, "perl_handler ENTER: SVs = %5d, OBJs = %5d\n", (int)sv_count, (int)sv_objcount)); ENTER; SAVETMPS; if (gv) { save_hptr(&GvHV(gv)); } if (endav) { save_aptr(&endav); endav = Nullav; } /* hookup STDIN & STDOUT to the client */ perl_stdout2client(r); perl_stdin2client(r); if(!cfg) { cfg = perl_create_request_config(r->pool, r->server); set_module_config(r->request_config, &perl_module, cfg); } cfg->setup_env = 1; PERL_CALLBACK("PerlHandler", cld->PerlHandler); cfg->setup_env = 0; FREETMPS; LEAVE; MP_TRACE_g(fprintf(stderr, "perl_handler LEAVE: SVs = %5d, OBJs = %5d\n", (int)sv_count, (int)sv_objcount)); if (r->prev && (r->prev->status != HTTP_OK) && mod_perl_sent_header(r, 0)) { /* avoid recursive error for ErrorDocuments */ status = OK; } (void)release_mutex(mod_perl_mutex); return status; } #ifdef PERL_CHILD_INIT typedef struct { server_rec *server; pool *pool; } server_hook_args; static void perl_child_exit_cleanup(void *data) { server_hook_args *args = (server_hook_args *)data; PERL_CHILD_EXIT_HOOK(args->server, args->pool); } void PERL_CHILD_INIT_HOOK(server_rec *s, pool *p) { char *hook = "PerlChildInitHandler"; dSTATUS; dPSRV(s); request_rec *r = mp_fake_request_rec(s, p, hook); server_hook_args *args = (server_hook_args *)palloc(p, sizeof(server_hook_args)); args->server = s; args->pool = p; register_cleanup(p, args, perl_child_exit_cleanup, null_cleanup); mod_perl_init_ids(); Apache__ServerStarting(FALSE); PERL_CALLBACK(hook, cls->PerlChildInitHandler); } #endif #ifdef PERL_CHILD_EXIT void PERL_CHILD_EXIT_HOOK(server_rec *s, pool *p) { char *hook = "PerlChildExitHandler"; dSTATUS; dPSRV(s); request_rec *r = mp_fake_request_rec(s, p, hook); PERL_CALLBACK(hook, cls->PerlChildExitHandler); perl_shutdown(s,p); } #endif static int do_proxy (request_rec *r) { return r->parsed_uri.scheme && !(r->parsed_uri.hostname && strEQ(r->parsed_uri.scheme, ap_http_method(r)) && ap_matches_request_vhost(r, r->parsed_uri.hostname, r->parsed_uri.port_str ? r->parsed_uri.port : ap_default_port(r))); } #ifdef PERL_POST_READ_REQUEST int PERL_POST_READ_REQUEST_HOOK(request_rec *r) { dSTATUS; dPSRV(r->server); #ifdef PERL_TRANS #if MODULE_MAGIC_NUMBER > 19980270 if (cls->PerlTransHandler && do_proxy(r)) { r->proxyreq = 1; r->uri = r->unparsed_uri; } #endif #endif #ifdef PERL_INIT PERL_CALLBACK("PerlInitHandler", cls->PerlInitHandler); #endif PERL_CALLBACK("PerlPostReadRequestHandler", cls->PerlPostReadRequestHandler); return status; } #endif #ifdef PERL_TRANS int PERL_TRANS_HOOK(request_rec *r) { dSTATUS; dPSRV(r->server); PERL_CALLBACK("PerlTransHandler", cls->PerlTransHandler); return status; } #endif #ifdef PERL_HEADER_PARSER int PERL_HEADER_PARSER_HOOK(request_rec *r) { dSTATUS; dPPDIR; #ifdef PERL_INIT PERL_CALLBACK("PerlInitHandler", cld->PerlInitHandler); #endif PERL_CALLBACK("PerlHeaderParserHandler", cld->PerlHeaderParserHandler); return status; } #endif #ifdef PERL_AUTHEN int PERL_AUTHEN_HOOK(request_rec *r) { dSTATUS; dPPDIR; PERL_CALLBACK("PerlAuthenHandler", cld->PerlAuthenHandler); return status; } #endif #ifdef PERL_AUTHZ int PERL_AUTHZ_HOOK(request_rec *r) { dSTATUS; dPPDIR; PERL_CALLBACK("PerlAuthzHandler", cld->PerlAuthzHandler); return status; } #endif #ifdef PERL_ACCESS int PERL_ACCESS_HOOK(request_rec *r) { dSTATUS; dPPDIR; PERL_CALLBACK("PerlAccessHandler", cld->PerlAccessHandler); return status; } #endif #ifdef PERL_TYPE int PERL_TYPE_HOOK(request_rec *r) { dSTATUS; dPPDIR; PERL_CALLBACK("PerlTypeHandler", cld->PerlTypeHandler); return status; } #endif #ifdef PERL_FIXUP int PERL_FIXUP_HOOK(request_rec *r) { dSTATUS; dPPDIR; PERL_CALLBACK("PerlFixupHandler", cld->PerlFixupHandler); return status; } #endif #ifdef PERL_LOG int PERL_LOG_HOOK(request_rec *r) { dSTATUS; dPPDIR; PERL_CALLBACK("PerlLogHandler", cld->PerlLogHandler); return status; } #endif #ifdef PERL_STACKED_HANDLERS #define CleanupHandler \ ((cld->PerlCleanupHandler && SvREFCNT(cld->PerlCleanupHandler)) ? cld->PerlCleanupHandler : Nullav) #else #define CleanupHandler cld->PerlCleanupHandler #endif #ifdef PERL_TRACE static char *my_signame(I32 num) { #ifdef psig_name return Perl_psig_name[num] ? SvPV(Perl_psig_name[num],na) : "?"; #else return PL_sig_name[num]; #endif } #endif static void per_request_cleanup(request_rec *r) { dPPREQ; #ifndef WIN32 perl_request_sigsave **sigs; int i; #endif if(!cfg) { return; } if(cfg->pnotes) { hv_clear(cfg->pnotes); SvREFCNT_dec(cfg->pnotes); cfg->pnotes = Nullhv; } #ifndef WIN32 sigs = (perl_request_sigsave **)cfg->sigsave->elts; for (i=0; i < cfg->sigsave->nelts; i++) { MP_TRACE_g(fprintf(stderr, "mod_perl: restoring SIG%s (%d) handler from: 0x%lx to: 0x%lx\n", my_signame(sigs[i]->signo), (int)sigs[i]->signo, (unsigned long)rsignal_state(sigs[i]->signo), (unsigned long)sigs[i]->h)); rsignal(sigs[i]->signo, sigs[i]->h); } #endif } void mod_perl_end_cleanup(void *data) { request_rec *r = (request_rec *)data; dSTATUS; dPPDIR; #ifdef PERL_CLEANUP PERL_CALLBACK("PerlCleanupHandler", CleanupHandler); #endif MP_TRACE_g(fprintf(stderr, "perl_end_cleanup...")); perl_run_rgy_endav(r->uri); per_request_cleanup(r); /* clear %ENV */ perl_clear_env(); /* reset @INC */ av_undef(GvAV(incgv)); SvREFCNT_dec(GvAV(incgv)); GvAV(incgv) = Nullav; GvAV(incgv) = av_copy_array(orig_inc); /* reset $/ */ sv_setpvn(GvSV(gv_fetchpv("/", TRUE, SVt_PV)), "\n", 1); { dTHR; /* %@ */ hv_clear(ERRHV); } callbacks_this_request = 0; #ifdef PERL_STACKED_HANDLERS /* reset Apache->push_handlers, but don't clear ExitHandler */ #define CH_EXIT_KEY "PerlChildExitHandler" { SV *exith = Nullsv; if(hv_exists(stacked_handlers, CH_EXIT_KEY, 20)) { exith = *hv_fetch(stacked_handlers, CH_EXIT_KEY, 20, FALSE); /* inc the refcnt since hv_clear will dec it */ ++SvREFCNT(exith); } hv_clear(stacked_handlers); if(exith) hv_store(stacked_handlers, CH_EXIT_KEY, 20, exith, FALSE); } #endif #ifdef USE_SFIO PerlIO_flush(PerlIO_stdout()); #endif MP_TRACE_g(fprintf(stderr, "ok\n")); (void)release_mutex(mod_perl_mutex); } void mod_perl_cleanup_handler(void *data) { request_rec *r = (request_rec *)data; SV *cv; I32 i; dPPDIR; (void)acquire_mutex(mod_perl_mutex); MP_TRACE_h(fprintf(stderr, "running registered cleanup handlers...\n")); for(i=0; i<=AvFILL(cleanup_av); i++) { cv = *av_fetch(cleanup_av, i, 0); MARK_WHERE("registered cleanup", cv); perl_call_handler(cv, (request_rec *)r, Nullav); UNMARK_WHERE; } av_clear(cleanup_av); #ifndef WIN32 if(cld) MP_RCLEANUP_off(cld); #endif (void)release_mutex(mod_perl_mutex); } #ifdef PERL_METHOD_HANDLERS int perl_handler_ismethod(HV *pclass, char *sub) { CV *cv; HV *stash; GV *gv; SV *sv; int is_method=0; if(!sub) return 0; sv = newSVpv(sub,0); if(!(cv = sv_2cv(sv, &stash, &gv, FALSE))) { GV *gvp = gv_fetchmethod(pclass, sub); if (gvp) cv = GvCV(gvp); } #ifdef CVf_METHOD if (cv && (CvFLAGS(cv) & CVf_METHOD)) { is_method = 1; } #endif if (!is_method && (cv && SvPOK(cv))) { is_method = strnEQ(SvPVX(cv), "$$", 2); } MP_TRACE_h(fprintf(stderr, "checking if `%s' is a method...%s\n", sub, (is_method ? "yes" : "no"))); SvREFCNT_dec(sv); return is_method; } #endif void mod_perl_noop(void *data) {} void mod_perl_register_cleanup(request_rec *r, SV *sv) { dPPDIR; if(!MP_RCLEANUP(cld)) { (void)perl_request_rec(r); register_cleanup(r->pool, (void*)r, mod_perl_cleanup_handler, mod_perl_noop); MP_RCLEANUP_on(cld); if(cleanup_av == Nullav) cleanup_av = newAV(); } MP_TRACE_h(fprintf(stderr, "registering PerlCleanupHandler\n")); ++SvREFCNT(sv); av_push(cleanup_av, sv); } #ifdef PERL_STACKED_HANDLERS int mod_perl_push_handlers(SV *self, char *hook, SV *sub, AV *handlers) { int do_store=0, len=strlen(hook); SV **svp; if(self && SvTRUE(sub)) { if(handlers == Nullav) { svp = hv_fetch(stacked_handlers, hook, len, 0); MP_TRACE_h(fprintf(stderr, "fetching %s stack\n", hook)); if(svp && SvTRUE(*svp) && SvROK(*svp)) { handlers = (AV*)SvRV(*svp); } else { MP_TRACE_h(fprintf(stderr, "%s handlers stack undef, creating\n", hook)); handlers = newAV(); do_store = 1; } } if(SvROK(sub) && (SvTYPE(SvRV(sub)) == SVt_PVCV)) { MP_TRACE_h(fprintf(stderr, "pushing CODE ref into `%s' handlers\n", hook)); } else if(SvPOK(sub)) { if(do_store) { MP_TRACE_h(fprintf(stderr, "pushing `%s' into `%s' handlers\n", SvPV(sub,na), hook)); } else { MP_TRACE_d(fprintf(stderr, "pushing `%s' into `%s' handlers\n", SvPV(sub,na), hook)); } } else { warn("mod_perl_push_handlers: Not a subroutine name or CODE reference!"); } ++SvREFCNT(sub); av_push(handlers, sub); if(do_store) hv_store(stacked_handlers, hook, len, (SV*)newRV_noinc((SV*)handlers), 0); return 1; } return 0; } int perl_run_stacked_handlers(char *hook, request_rec *r, AV *handlers) { dSTATUS; I32 i, do_clear=FALSE; SV *sub, **svp; int hook_len = strlen(hook); #ifdef USE_ITHREADS dTHX; if (!aTHX) { PERL_SET_CONTEXT(perl); } #endif if(handlers == Nullav) { if(hv_exists(stacked_handlers, hook, hook_len)) { svp = hv_fetch(stacked_handlers, hook, hook_len, 0); if(svp && SvROK(*svp)) handlers = (AV*)SvRV(*svp); } else { MP_TRACE_h(fprintf(stderr, "`%s' push_handlers() stack is empty\n", hook)); return NO_HANDLERS; } do_clear = TRUE; MP_TRACE_h(fprintf(stderr, "running %d pushed (stacked) handlers for %s...\n", (int)AvFILL(handlers)+1, r->uri)); } else { #ifdef PERL_STACKED_HANDLERS /* XXX: bizarre, I only see this with httpd.conf.pl and PerlAccessHandler */ if(SvTYPE((SV*)handlers) != SVt_PVAV) { #if MODULE_MAGIC_NUMBER > 19970909 aplog_error(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r->server, #else fprintf(stderr, #endif "[warning] %s stack is not an ARRAY!\n", hook); sv_dump((SV*)handlers); return DECLINED; } #endif MP_TRACE_h(fprintf(stderr, "running %d server configured stacked handlers for %s...\n", (int)AvFILL(handlers)+1, r->uri)); } for(i=0; i<=AvFILL(handlers); i++) { MP_TRACE_h(fprintf(stderr, "calling &{%s->[%d]} (%d total)\n", hook, (int)i, (int)AvFILL(handlers)+1)); if(!(sub = *av_fetch(handlers, i, FALSE))) { MP_TRACE_h(fprintf(stderr, "sub not defined!\n")); } else { if(!SvTRUE(sub)) { MP_TRACE_h(fprintf(stderr, "sub undef! skipping callback...\n")); continue; } MARK_WHERE(hook, sub); status = perl_call_handler(sub, r, Nullav); UNMARK_WHERE; MP_TRACE_h(fprintf(stderr, "&{%s->[%d]} returned status=%d\n", hook, (int)i, status)); if((status != OK) && (status != DECLINED)) { if(do_clear) av_clear(handlers); return status; } } } if(do_clear) av_clear(handlers); return status; } #endif /* PERL_STACKED_HANDLERS */ /* things to do once per-request */ void perl_per_request_init(request_rec *r) { dPPDIR; dPPREQ; /* PerlSendHeader */ if(MP_SENDHDR(cld)) { MP_SENTHDR_off(cld); table_set(r->subprocess_env, "PERL_SEND_HEADER", "On"); } else MP_SENTHDR_on(cld); if(!cfg) { cfg = perl_create_request_config(r->pool, r->server); set_module_config(r->request_config, &perl_module, cfg); } else if (cfg->setup_env && MP_ENV(cld)) { perl_setup_env(r); cfg->setup_env = 0; /* just once per-request */ } if (cfg->dir_env != cld->env) { /* PerlSetEnv * update only if the table changes across a request */ MP_HASENV_on(cld); mod_perl_dir_env(r, cld); cfg->dir_env = cld->env; } if(callbacks_this_request++ > 0) return; if (!r->main) { /* so Apache->request will work before PerlHandler with CGI.pm * XXX: triggers core dump in subrequests, * so just do in the main request for now */ (void)perl_request_rec(r); } /* SetEnv PERL5LIB */ if (!MP_INCPUSH(cld)) { char *path = (char *)table_get(r->subprocess_env, "PERL5LIB"); if (path) { perl_inc_unshift(path); MP_INCPUSH_on(cld); } } { dPSRV(r->server); mod_perl_pass_env(r->pool, cls); } mod_perl_tie_scriptname(); /* will be released in mod_perl_end_cleanup */ (void)acquire_mutex(mod_perl_mutex); register_cleanup(r->pool, (void*)r, mod_perl_end_cleanup, mod_perl_noop); #ifdef WIN32 sv_setpvf(perl_get_sv("Apache::CurrentThreadId", TRUE), "0x%lx", (unsigned long)GetCurrentThreadId()); #endif /* hookup stderr to error_log */ #ifndef PERL_TRACE if(r->server->error_log) error_log2stderr(r->server); #endif seqno++; MP_TRACE_g(fprintf(stderr, "mod_perl: inc seqno to %d for %s\n", seqno, r->uri)); seqno_check_max(r, seqno); /* set $$, $>, etc., if 1.3a1+, this really happens during child_init */ perl_init_ids; } /* XXX this still needs work, getting there... */ int perl_call_handler(SV *sv, request_rec *r, AV *args) { int count, status, is_method=0; dSP; perl_dir_config *cld = NULL; HV *stash = Nullhv; SV *pclass = newSVsv(sv), *dispsv = Nullsv; CV *cv = Nullcv; char *method = "handler"; int defined_sub = 0, anon = 0; char *dispatcher = NULL; if(r->per_dir_config) cld = (perl_dir_config *) get_module_config(r->per_dir_config, &perl_module); #ifdef PERL_DISPATCH if(cld && (dispatcher = cld->PerlDispatchHandler)) { if(!(dispsv = (SV*)perl_get_cv(dispatcher, FALSE))) { if(strlen(dispatcher) > 0) { /* XXX */ fprintf(stderr, "mod_perl: unable to fetch PerlDispatchHandler `%s'\n", dispatcher); } dispatcher = NULL; } } #endif if(r->per_dir_config) perl_per_request_init(r); if(!dispatcher && (SvTYPE(sv) == SVt_PV)) { char *imp = pstrdup(r->pool, (char *)SvPV(pclass,na)); if((anon = strnEQ(imp,"sub ",4))) { sv = perl_eval_pv(imp, FALSE); MP_TRACE_h(fprintf(stderr, "perl_call: caching CV pointer to `__ANON__'\n")); defined_sub++; goto callback; /* XXX, I swear I've never used goto before! */ } #ifdef PERL_METHOD_HANDLERS { char *end_pclass = NULL; if ((end_pclass = strstr(imp, "->"))) { end_pclass[0] = '\0'; if(pclass) SvREFCNT_dec(pclass); pclass = newSVpv(imp, 0); end_pclass[0] = ':'; end_pclass[1] = ':'; method = &end_pclass[2]; imp = method; ++is_method; } } if(*SvPVX(pclass) == '$') { SV *obj = perl_eval_pv(SvPVX(pclass), TRUE); if(SvROK(obj) && sv_isobject(obj)) { MP_TRACE_h(fprintf(stderr, "handler object %s isa %s\n", SvPVX(pclass), HvNAME(SvSTASH((SV*)SvRV(obj))))); SvREFCNT_dec(pclass); pclass = obj; ++SvREFCNT(pclass); /* this will _dec later */ stash = SvSTASH((SV*)SvRV(pclass)); } } if(pclass && !stash) stash = gv_stashpv(SvPV(pclass,na),FALSE); #if 0 MP_TRACE_h(fprintf(stderr, "perl_call: pclass=`%s'\n", SvPV(pclass,na))); MP_TRACE_h(fprintf(stderr, "perl_call: imp=`%s'\n", imp)); MP_TRACE_h(fprintf(stderr, "perl_call: method=`%s'\n", method)); MP_TRACE_h(fprintf(stderr, "perl_call: stash=`%s'\n", stash ? HvNAME(stash) : "unknown")); #endif #else method = NULL; /* avoid warning */ #endif /* if a Perl*Handler is not a defined function name, * default to the class implementor's handler() function * attempt to load the class module if it is not already */ if(!imp) imp = SvPV(sv,na); if(!stash) stash = gv_stashpv(imp,FALSE); if(!is_method) defined_sub = (cv = perl_get_cv(imp, FALSE)) ? TRUE : FALSE; #ifdef PERL_METHOD_HANDLERS if(!defined_sub && stash) { GV *gvp; MP_TRACE_h(fprintf(stderr, "perl_call: trying method lookup on `%s' in class `%s'...", method, HvNAME(stash))); /* XXX Perl caches method lookups internally, * should we cache this lookup? */ if((gvp = gv_fetchmethod(stash, method))) { cv = GvCV(gvp); MP_TRACE_h(fprintf(stderr, "found\n")); is_method = perl_handler_ismethod(stash, method); } else { MP_TRACE_h(fprintf(stderr, "not found\n")); } } #endif if(!stash && !defined_sub) { MP_TRACE_h(fprintf(stderr, "%s symbol table not found, loading...\n", imp)); if(perl_require_module(imp, r->server) == OK) stash = gv_stashpv(imp,FALSE); #ifdef PERL_METHOD_HANDLERS if(stash) /* check again */ is_method = perl_handler_ismethod(stash, method); #endif SPAGAIN; /* reset stack pointer after require() */ } if(!is_method && !defined_sub) { MP_TRACE_h(fprintf(stderr, "perl_call: defaulting to %s::handler\n", imp)); sv_catpv(sv, "::handler"); } #if 0 /* XXX: CV lookup cache disabled for now */ if(!is_method && defined_sub) { /* cache it */ MP_TRACE_h(fprintf(stderr, "perl_call: caching CV pointer to `%s'\n", (anon ? "__ANON__" : SvPV(sv,na)))); SvREFCNT_dec(sv); sv = (SV*)newRV((SV*)cv); /* let newRV inc the refcnt */ } #endif } else { MP_TRACE_h(fprintf(stderr, "perl_call: handler is a %s\n", dispatcher ? "dispatcher" : "cached CV")); } callback: ENTER; SAVETMPS; PUSHMARK(sp); #ifdef PERL_METHOD_HANDLERS if(is_method) XPUSHs(sv_2mortal(pclass)); else SvREFCNT_dec(pclass); #else SvREFCNT_dec(pclass); #endif XPUSHs((SV*)perl_bless_request_rec(r)); if(dispatcher) { MP_TRACE_h(fprintf(stderr, "mod_perl: handing off to PerlDispatchHandler `%s'\n", dispatcher)); /*XPUSHs(sv_mortalcopy(sv));*/ XPUSHs(sv); sv = dispsv; } { I32 i, len = (args ? AvFILL(args) : 0); if(args) { EXTEND(sp, len); for(i=0; i<=len; i++) PUSHs(sv_2mortal(*av_fetch(args, i, FALSE))); } } PUTBACK; /* use G_EVAL so we can trap errors */ #ifdef PERL_METHOD_HANDLERS if(is_method) count = perl_call_method(method, G_EVAL | G_SCALAR); else #endif count = perl_call_sv(sv, G_EVAL | G_SCALAR); SPAGAIN; if ((status = perl_eval_ok(r->server)) != OK) { dTHRCTX; if (status == SERVER_ERROR) { MP_STORE_ERROR(r->uri, ERRSV); if (r->notes) { ap_table_set(r->notes, "error-notes", SvPVX(ERRSV)); } } else if (status == DECLINED) { status = r->status == 200 ? OK : r->status; } } else if(count != 1) { mod_perl_error(r->server, "perl_call did not return a status arg, assuming OK"); status = OK; } else { status = POPi; if((status == 1) || (status == 200) || (status > 600)) status = OK; if((status == SERVER_ERROR) && ERRSV_CAN_BE_HTTP) { SV *errsv = Nullsv; if(MP_EXISTS_ERROR(r->uri) && (errsv = MP_FETCH_ERROR(r->uri))) { (void)perl_sv_is_http_code(errsv, &status); } } } PUTBACK; FREETMPS; LEAVE; MP_TRACE_g(fprintf(stderr, "perl_call_handler: SVs = %5d, OBJs = %5d\n", (int)sv_count, (int)sv_objcount)); { dTHRCTX; if(SvMAGICAL(ERRSV)) sv_unmagic(ERRSV, 'U'); /* Apache::exit was called */ } return status; } request_rec *perl_request_rec(request_rec *r) { if(r != NULL) { mp_request_rec = (IV)r; return NULL; } else return (request_rec *)mp_request_rec; } SV *perl_bless_request_rec(request_rec *r) { SV *sv = sv_newmortal(); sv_setref_pv(sv, "Apache", (void*)r); MP_TRACE_g(fprintf(stderr, "blessing request_rec=(0x%lx)\n", (unsigned long)r)); return sv; } void perl_setup_env(request_rec *r) { int i; array_header *arr = perl_cgi_env_init(r); table_entry *elts = (table_entry *)arr->elts; for (i = 0; i < arr->nelts; ++i) { if (!elts[i].key || !elts[i].val) continue; mp_setenv(elts[i].key, elts[i].val); } MP_TRACE_g(fprintf(stderr, "perl_setup_env...%d keys\n", i)); } int mod_perl_seqno(SV *self, int inc) { self = self; /*avoid warning*/ if(inc) seqno += inc; return seqno; }