blob: 62d40303bbb3788ed7f63f069096ea37878ee7bd [file] [log] [blame]
/* 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"
#define EnvMgOK ((SV*)ENVHV && SvMAGIC((SV*)ENVHV))
#define EnvMgObj (EnvMgOK ? SvMAGIC((SV*)ENVHV)->mg_ptr : NULL)
#define EnvMgLen (EnvMgOK ? SvMAGIC((SV*)ENVHV)->mg_len : 0)
#define EnvMgObjSet(val){ \
if (EnvMgOK) SvMAGIC((SV*)ENVHV)->mg_ptr = (char *)val;}
#define EnvMgLenSet(val) {\
if (EnvMgOK) SvMAGIC((SV*)ENVHV)->mg_len = val;}
/* XXX: move to utils? */
static unsigned long modperl_interp_address(pTHX)
{
#ifdef USE_ITHREADS
return (unsigned long)aTHX;
#else
return (unsigned long)0; /* just one interpreter */
#endif
}
#define MP_ENV_HV_STORE(hv, key, val) STMT_START { \
I32 klen = strlen(key); \
SV **svp = hv_fetch(hv, key, klen, FALSE); \
SV *sv; \
\
if (svp) { \
sv_setpv(*svp, val); \
} \
else { \
sv = newSVpv(val, 0); \
(void)hv_store(hv, key, klen, sv, FALSE); \
modperl_envelem_tie(sv, key, klen); \
svp = &sv; \
} \
MP_TRACE_e(MP_FUNC, "$ENV{%s} = \"%s\";", key, val); \
\
SvTAINTED_on(*svp); \
} STMT_END
void modperl_env_hv_store(pTHX_ const char *key, const char *val)
{
MP_ENV_HV_STORE(ENVHV, key, val);
}
static MP_INLINE
void modperl_env_hv_delete(pTHX_ HV *hv, char *key)
{
I32 klen = strlen(key);
if (hv_exists(hv, key, klen)) {
(void)hv_delete(hv, key, strlen(key), G_DISCARD);
}
}
typedef struct {
const char *key;
I32 klen;
const char *val;
I32 vlen;
U32 hash;
} modperl_env_ent_t;
#define MP_ENV_ENT(k,v) \
{ k, MP_SSTRLEN(k), v, MP_SSTRLEN(v), 0 }
static modperl_env_ent_t MP_env_const_vars[] = {
MP_ENV_ENT("MOD_PERL", MP_VERSION_STRING),
MP_ENV_ENT("MOD_PERL_API_VERSION", MP_API_VERSION),
{ NULL }
};
void modperl_env_hash_keys(pTHX)
{
modperl_env_ent_t *ent = MP_env_const_vars;
while (ent->key) {
PERL_HASH(ent->hash, ent->key, ent->klen);
MP_TRACE_e(MP_FUNC, "[0x%lx] PERL_HASH: %s (len: %d)",
modperl_interp_address(aTHX), ent->key, ent->klen);
ent++;
}
}
void modperl_env_clear(pTHX)
{
HV *hv = ENVHV;
U32 mg_flags;
modperl_env_untie(mg_flags);
MP_TRACE_e(MP_FUNC, "[0x%lx] %%ENV = ();", modperl_interp_address(aTHX));
hv_clear(hv);
modperl_env_tie(mg_flags);
}
#define MP_ENV_HV_STORE_TABLE_ENTRY(hv, elt) \
MP_ENV_HV_STORE(hv, elt.key, elt.val);
static void modperl_env_table_populate(pTHX_ apr_table_t *table)
{
HV *hv = ENVHV;
U32 mg_flags;
int i;
const apr_array_header_t *array;
apr_table_entry_t *elts;
modperl_env_init(aTHX);
modperl_env_untie(mg_flags);
array = apr_table_elts(table);
elts = (apr_table_entry_t *)array->elts;
for (i = 0; i < array->nelts; i++) {
if (!elts[i].key || !elts[i].val) {
continue;
}
MP_ENV_HV_STORE_TABLE_ENTRY(hv, elts[i]);
}
modperl_env_tie(mg_flags);
}
static void modperl_env_table_unpopulate(pTHX_ apr_table_t *table)
{
HV *hv = ENVHV;
U32 mg_flags;
int i;
const apr_array_header_t *array;
apr_table_entry_t *elts;
modperl_env_untie(mg_flags);
array = apr_table_elts(table);
elts = (apr_table_entry_t *)array->elts;
for (i = 0; i < array->nelts; i++) {
if (!elts[i].key) {
continue;
}
modperl_env_hv_delete(aTHX_ hv, elts[i].key);
MP_TRACE_e(MP_FUNC, "delete $ENV{%s};", elts[i].key);
}
modperl_env_tie(mg_flags);
}
/* see the comment in modperl_env_sync_env_hash2table */
static void modperl_env_sync_table(pTHX_ apr_table_t *table)
{
int i;
const apr_array_header_t *array;
apr_table_entry_t *elts;
HV *hv = ENVHV;
SV **svp;
array = apr_table_elts(table);
elts = (apr_table_entry_t *)array->elts;
for (i = 0; i < array->nelts; i++) {
if (!elts[i].key) {
continue;
}
svp = hv_fetch(hv, elts[i].key, strlen(elts[i].key), FALSE);
if (svp) {
apr_table_set(table, elts[i].key, SvPV_nolen(*svp));
MP_TRACE_e(MP_FUNC, "(Set|Pass)Env '%s' '%s'", elts[i].key,
SvPV_nolen(*svp));
}
}
TAINT_NOT; /* SvPV_* causes the taint issue */
}
/* Make per-server PerlSetEnv and PerlPassEnv in sync with %ENV at
* config time (if perl is running), by copying %ENV values to the
* PerlSetEnv and PerlPassEnv tables (only for keys which are already
* in those tables)
*/
void modperl_env_sync_srv_env_hash2table(pTHX_ apr_pool_t *p,
modperl_config_srv_t *scfg)
{
modperl_env_sync_table(aTHX_ scfg->SetEnv);
modperl_env_sync_table(aTHX_ scfg->PassEnv);
}
void modperl_env_sync_dir_env_hash2table(pTHX_ apr_pool_t *p,
modperl_config_dir_t *dcfg)
{
modperl_env_sync_table(aTHX_ dcfg->SetEnv);
}
/* list of environment variables to pass by default */
static const char *MP_env_pass_defaults[] = {
"PATH", "TZ", NULL
};
void modperl_env_configure_server(pTHX_ apr_pool_t *p, server_rec *s)
{
MP_dSCFG(s);
int i = 0;
/* make per-server PerlSetEnv and PerlPassEnv entries visible
* to %ENV at config time
*/
for (i=0; MP_env_pass_defaults[i]; i++) {
const char *key = MP_env_pass_defaults[i];
char *val;
if (apr_table_get(scfg->SetEnv, key) ||
apr_table_get(scfg->PassEnv, key))
{
continue; /* already configured */
}
if ((val = getenv(key))) {
apr_table_set(scfg->PassEnv, key, val);
}
}
MP_TRACE_e(MP_FUNC, "\t[0x%lx/%s]"
"\n\t@ENV{keys scfg->SetEnv} = values scfg->SetEnv;",
modperl_interp_address(aTHX),
modperl_server_desc(s, p));
modperl_env_table_populate(aTHX_ scfg->SetEnv);
MP_TRACE_e(MP_FUNC, "\t[0x%lx/%s]"
"\n\t@ENV{keys scfg->PassEnv} = values scfg->PassEnv;",
modperl_interp_address(aTHX),
modperl_server_desc(s, p));
modperl_env_table_populate(aTHX_ scfg->PassEnv);
}
#define overlay_subprocess_env(r, tab) \
r->subprocess_env = apr_table_overlay(r->pool, \
r->subprocess_env, \
tab)
void modperl_env_configure_request_dir(pTHX_ request_rec *r)
{
MP_dRCFG;
MP_dDCFG;
/* populate %ENV and r->subprocess_env with per-directory
* PerlSetEnv entries.
*
* note that per-server PerlSetEnv entries, as well as
* PerlPassEnv entries (which are only per-server), are added
* to %ENV and r->subprocess_env via modperl_env_configure_request_srv
*/
if (!apr_is_empty_table(dcfg->SetEnv)) {
apr_table_t *setenv_copy;
/* add per-directory PerlSetEnv entries to %ENV
* collisions with per-server PerlSetEnv entries are
* resolved via the nature of a Perl hash
*/
MP_TRACE_e(MP_FUNC, "\t[0x%lx/%s]"
"\n\t@ENV{keys dcfg->SetEnv} = values dcfg->SetEnv;",
modperl_interp_address(aTHX),
modperl_server_desc(r->server, r->pool));
modperl_env_table_populate(aTHX_ dcfg->SetEnv);
/* make sure the entries are in the subprocess_env table as well.
* we need to use apr_table_overlap (not apr_table_overlay) because
* r->subprocess_env might have per-server PerlSetEnv entries in it
* and using apr_table_overlay would generate duplicate entries.
* in order to use apr_table_overlap, though, we need to copy the
* the dcfg table so that pool requirements are satisfied */
setenv_copy = apr_table_copy(r->pool, dcfg->SetEnv);
apr_table_overlap(r->subprocess_env, setenv_copy, APR_OVERLAP_TABLES_SET);
}
MpReqPERL_SET_ENV_DIR_On(rcfg);
}
void modperl_env_configure_request_srv(pTHX_ request_rec *r)
{
MP_dRCFG;
MP_dSCFG(r->server);
/* populate %ENV and r->subprocess_env with per-server PerlSetEnv
* and PerlPassEnv entries.
*
* although both are setup in %ENV in modperl_request_configure_server
* %ENV will be reset via modperl_env_request_unpopulate.
*/
if (!apr_is_empty_table(scfg->SetEnv)) {
MP_TRACE_e(MP_FUNC, "\t[0x%lx/%s]"
"\n\t@ENV{keys scfg->SetEnv} = values scfg->SetEnv;",
modperl_interp_address(aTHX),
modperl_server_desc(r->server, r->pool));
modperl_env_table_populate(aTHX_ scfg->SetEnv);
overlay_subprocess_env(r, scfg->SetEnv);
}
if (!apr_is_empty_table(scfg->PassEnv)) {
MP_TRACE_e(MP_FUNC, "\t[0x%lx/%s]"
"\n\t@ENV{keys scfg->PassEnv} = values scfg->PassEnv;",
modperl_interp_address(aTHX),
modperl_server_desc(r->server, r->pool));
modperl_env_table_populate(aTHX_ scfg->PassEnv);
overlay_subprocess_env(r, scfg->PassEnv);
}
MpReqPERL_SET_ENV_SRV_On(rcfg);
}
void modperl_env_default_populate(pTHX)
{
modperl_env_ent_t *ent = MP_env_const_vars;
HV *hv = ENVHV;
U32 mg_flags;
modperl_env_untie(mg_flags);
while (ent->key) {
SV *sv = newSVpvn(ent->val, ent->vlen);
(void)hv_store(hv, ent->key, ent->klen,
sv, ent->hash);
MP_TRACE_e(MP_FUNC, "$ENV{%s} = \"%s\";", ent->key, ent->val);
modperl_envelem_tie(sv, ent->key, ent->klen);
ent++;
}
modperl_env_tie(mg_flags);
}
void modperl_env_request_populate(pTHX_ request_rec *r)
{
MP_dRCFG;
/* this is called under the following conditions
* - if PerlOptions +SetupEnv
* - if $r->subprocess_env() is called in a void context with no args
*
* normally, %ENV is only populated once per request (if at all) -
* just prior to content generation if +SetupEnv.
*
* however, in the $r->subprocess_env() case it will be called
* more than once - once for each void call, and once again just
* prior to content generation. while costly, the multiple
* passes are required, otherwise void calls would prohibit later
* phases from populating %ENV with new subprocess_env table entries
*/
MP_TRACE_e(MP_FUNC, "\t[0x%lx/%s%s]"
"\n\t@ENV{keys r->subprocess_env} = values r->subprocess_env;",
modperl_interp_address(aTHX),
modperl_server_desc(r->server, r->pool), r->uri);
/* we can eliminate some of the cost by only doing CGI variables once
* per-request no matter how many times $r->subprocess_env() is called
*/
if (! MpReqSETUP_ENV(rcfg)) {
ap_add_common_vars(r);
ap_add_cgi_vars(r);
}
modperl_env_table_populate(aTHX_ r->subprocess_env);
/* don't set up CGI variables again this request.
* this also triggers modperl_env_request_unpopulate, which
* resets %ENV between requests - see modperl_config_request_cleanup
*/
MpReqSETUP_ENV_On(rcfg);
}
void modperl_env_request_unpopulate(pTHX_ request_rec *r)
{
MP_dRCFG;
/* unset only once */
if (!MpReqSETUP_ENV(rcfg)) {
return;
}
MP_TRACE_e(MP_FUNC,
"\n\t[0x%lx/%s%s]\n\tdelete @ENV{keys r->subprocess_env};",
modperl_interp_address(aTHX),
modperl_server_desc(r->server, r->pool), r->uri);
modperl_env_table_unpopulate(aTHX_ r->subprocess_env);
MpReqSETUP_ENV_Off(rcfg);
}
void modperl_env_request_tie(pTHX_ request_rec *r)
{
EnvMgObjSet(r);
EnvMgLenSet(-1);
#ifdef MP_PERL_HV_GMAGICAL_AWARE
MP_TRACE_e(MP_FUNC, "[0x%lx] tie %%ENV, $r\t (%s%s)",
modperl_interp_address(aTHX),
modperl_server_desc(r->server, r->pool), r->uri);
SvGMAGICAL_on((SV*)ENVHV);
#endif
}
void modperl_env_request_untie(pTHX_ request_rec *r)
{
EnvMgObjSet(NULL);
#ifdef MP_PERL_HV_GMAGICAL_AWARE
MP_TRACE_e(MP_FUNC, "[0x%lx] untie %%ENV; # from r\t (%s%s)",
modperl_interp_address(aTHX),
modperl_server_desc(r->server, r->pool), r->uri);
SvGMAGICAL_off((SV*)ENVHV);
#endif
}
/* handy access to perl's original virtual tables
*/
#define MP_PL_vtbl_call(name, meth) \
PL_vtbl_##name.svt_##meth(aTHX_ sv, mg)
#define MP_dENV_KEY \
STRLEN klen; \
const char *key = (const char *)MgPV(mg,klen)
#define MP_dENV_VAL \
STRLEN vlen; \
const char *val = (const char *)SvPV(sv,vlen)
/*
* XXX: what we do here might change:
* - make it optional for %ENV to be tied to r->subprocess_env
* - make it possible to modify environ
* - we could allow modification of environ if mpm isn't threaded
* - we could allow modification of environ if variable isn't a CGI
* variable (still could cause problems)
*/
/*
* problems we are trying to solve:
* - environ is shared between threads
* + Perl does not serialize access to environ
* + even if it did, CGI variables cannot be shared between threads!
* problems we create by trying to solve above problems:
* - a forked process will not inherit the current %ENV
* - C libraries might rely on environ, e.g. DBD::Oracle
*/
static int modperl_env_magic_set_all(pTHX_ SV *sv, MAGIC *mg)
{
request_rec *r = (request_rec *)EnvMgObj;
if (r) {
if (PL_localizing) {
/* local %ENV = (FOO => 'bar', BIZ => 'baz') */
HE *entry;
STRLEN n_a;
hv_iterinit((HV*)sv);
while ((entry = hv_iternext((HV*)sv))) {
I32 keylen;
apr_table_set(r->subprocess_env,
hv_iterkey(entry, &keylen),
SvPV(hv_iterval((HV*)sv, entry), n_a));
MP_TRACE_e(MP_FUNC, "[0x%lx] localizing: %s => %s",
modperl_interp_address(aTHX),
hv_iterkey(entry, &keylen),
SvPV(hv_iterval((HV*)sv, entry), n_a));
}
}
}
else {
#ifdef MP_TRACE
HE *entry;
STRLEN n_a;
MP_TRACE_e(MP_FUNC, "\n\t[0x%lx] populating %%ENV:",
modperl_interp_address(aTHX));
hv_iterinit((HV*)sv);
while ((entry = hv_iternext((HV*)sv))) {
I32 keylen;
MP_TRACE_e(MP_FUNC, "$ENV{%s} = \"%s\";",
hv_iterkey(entry, &keylen),
SvPV(hv_iterval((HV*)sv, entry), n_a));
}
#endif
return MP_PL_vtbl_call(env, set);
}
return 0;
}
static int modperl_env_magic_clear_all(pTHX_ SV *sv, MAGIC *mg)
{
request_rec *r = (request_rec *)EnvMgObj;
if (r) {
apr_table_clear(r->subprocess_env);
MP_TRACE_e(MP_FUNC, "[0x%lx] clearing all magic off r->subprocess_env",
modperl_interp_address(aTHX));
}
else {
MP_TRACE_e(MP_FUNC, "[0x%lx] %%ENV = ();",
modperl_interp_address(aTHX));
return MP_PL_vtbl_call(env, clear);
}
return 0;
}
static int modperl_env_magic_copy(pTHX_ SV *sv, MAGIC *mg, SV *nsv, const char *name, I32 namlen)
{
MP_TRACE_e(MP_FUNC, "setting up %%ENV element magic");
sv_magicext(nsv, mg->mg_obj, toLOWER(mg->mg_type), &MP_vtbl_envelem, name, namlen);
return 1;
}
static int modperl_env_magic_local_all(pTHX_ SV *nsv, MAGIC *mg)
{
MAGIC *nmg;
MP_TRACE_e(MP_FUNC, "localizing %%ENV");
nmg = sv_magicext(nsv, mg->mg_obj, mg->mg_type, &MP_vtbl_env, (char*)NULL, 0);
nmg->mg_ptr = mg->mg_ptr;
nmg->mg_flags |= MGf_COPY;
#if (PERL_REVISION == 5 && PERL_VERSION == 8 && PERL_SUBVERSION > 8) || \
MP_PERL_VERSION_AT_LEAST(5, 9, 3)
nmg->mg_flags |= MGf_LOCAL;
#endif
return 1;
}
static int modperl_env_magic_set(pTHX_ SV *sv, MAGIC *mg)
{
request_rec *r = (request_rec *)EnvMgObj;
if (r) {
MP_dENV_KEY;
MP_dENV_VAL;
apr_table_set(r->subprocess_env, key, val);
MP_TRACE_e(MP_FUNC, "[0x%lx] r->subprocess_env set: %s => %s",
modperl_interp_address(aTHX), key, val);
}
else {
#ifdef MP_TRACE
MP_dENV_KEY;
MP_dENV_VAL;
MP_TRACE_e(MP_FUNC,
"[0x%lx] $ENV{%s} = \"%s\";",
modperl_interp_address(aTHX), key, val);
#endif
return MP_PL_vtbl_call(envelem, set);
}
return 0;
}
static int modperl_env_magic_clear(pTHX_ SV *sv, MAGIC *mg)
{
request_rec *r = (request_rec *)EnvMgObj;
if (r) {
MP_dENV_KEY;
apr_table_unset(r->subprocess_env, key);
MP_TRACE_e(MP_FUNC, "[0x%lx] r->subprocess_env unset: %s",
modperl_interp_address(aTHX), key);
}
else {
#ifdef MP_TRACE
MP_dENV_KEY;
MP_TRACE_e(MP_FUNC, "[0x%lx] delete $ENV{%s};",
modperl_interp_address(aTHX), key);
#endif
return MP_PL_vtbl_call(envelem, clear);
}
return 0;
}
#ifdef MP_PERL_HV_GMAGICAL_AWARE
static int modperl_env_magic_get(pTHX_ SV *sv, MAGIC *mg)
{
request_rec *r = (request_rec *)EnvMgObj;
if (r) {
MP_dENV_KEY;
const char *val;
if ((val = apr_table_get(r->subprocess_env, key))) {
sv_setpv(sv, val);
MP_TRACE_e(MP_FUNC,
"[0x%lx] r->subprocess_env get: %s => %s",
modperl_interp_address(aTHX), key, val);
}
else {
sv_setsv(sv, &PL_sv_undef);
MP_TRACE_e(MP_FUNC,
"[0x%lx] r->subprocess_env get: %s => undef",
modperl_interp_address(aTHX), key);
}
}
else {
/* there is no svt_get in PL_vtbl_envelem */
#ifdef MP_TRACE
MP_dENV_KEY;
MP_TRACE_e(MP_FUNC,
"[0x%lx] there is no svt_get in PL_vtbl_envelem: %s",
modperl_interp_address(aTHX), key);
#endif
}
return 0;
}
#endif
/* override %ENV virtual tables with our own */
MGVTBL MP_vtbl_env = {
0,
modperl_env_magic_set_all,
0,
modperl_env_magic_clear_all,
0,
modperl_env_magic_copy,
0,
modperl_env_magic_local_all
};
MGVTBL MP_vtbl_envelem = {
0,
modperl_env_magic_set,
0,
modperl_env_magic_clear,
0
};
void modperl_env_init(pTHX)
{
MAGIC *mg;
/* Find the 'E' magic on %ENV */
if (!PL_envgv)
return;
if (!SvRMAGICAL(ENVHV))
return;
mg = mg_find((const SV *)ENVHV, PERL_MAGIC_env);
if (!mg)
return;
/* Ignore it if it isn't perl's original version */
if (mg->mg_virtual != &PL_vtbl_env)
return;
MP_TRACE_e(MP_FUNC, "env_init - ptr: %x obj: %x flags: %x",
mg->mg_ptr, mg->mg_obj, mg->mg_flags);
/* Remove it */
#if MP_PERL_VERSION_AT_LEAST(5, 13, 6)
mg_free_type((SV*)ENVHV, PERL_MAGIC_env);
#else
mg_free((SV*)ENVHV);
#endif
/* Add our version instead */
mg = sv_magicext((SV*)ENVHV, (SV*)NULL, PERL_MAGIC_env, &MP_vtbl_env, (char*)NULL, 0);
mg->mg_flags |= MGf_COPY;
#if (PERL_REVISION == 5 && PERL_VERSION == 8 && PERL_SUBVERSION > 8) || \
MP_PERL_VERSION_AT_LEAST(5, 9, 3)
mg->mg_flags |= MGf_LOCAL;
#endif
}
void modperl_env_unload(pTHX)
{
MAGIC *mg;
/* Find the 'E' magic on %ENV */
if (!PL_envgv)
return;
if (!SvRMAGICAL(ENVHV))
return;
mg = mg_find((const SV *)ENVHV, PERL_MAGIC_env);
if (!mg)
return;
/* Ignore it if it isn't our version */
if (mg->mg_virtual != &MP_vtbl_env)
return;
MP_TRACE_e(MP_FUNC, "env_unload - ptr: %x obj: %x flags: %x",
mg->mg_ptr, mg->mg_obj, mg->mg_flags);
/* Remove it */
#if MP_PERL_VERSION_AT_LEAST(5, 13, 6)
mg_free_type((SV*)ENVHV, PERL_MAGIC_env);
#else
mg_free((SV*)ENVHV);
#endif
/* Restore perl's original version */
sv_magicext((SV*)ENVHV, (SV*)NULL, PERL_MAGIC_env, &PL_vtbl_env, (char*)NULL, 0);
}
/*
* Local Variables:
* c-basic-offset: 4
* indent-tabs-mode: nil
* End:
*/