geoff 2004/02/11 18:05:29
Modified: . Changes src/modules/perl modperl_cmd.c modperl_config.c modperl_types.h modperl_util.c todo release Added: t/htdocs/merge2 htaccess t/modperl merge.t merge2.t merge3.t t/response/TestModperl merge.pm Log: fix PerlAddVar configuration merging (short explanation, lots of work) Revision Changes Path 1.327 +2 -0 modperl-2.0/Changes Index: Changes =================================================================== RCS file: /home/cvs/modperl-2.0/Changes,v retrieving revision 1.326 retrieving revision 1.327 diff -u -r1.326 -r1.327 --- Changes 9 Feb 2004 19:44:41 -0000 1.326 +++ Changes 12 Feb 2004 02:05:28 -0000 1.327 @@ -12,6 +12,8 @@ =item 1.99_13-dev +fix PerlAddVar configuration merging [Geoffrey Young] + Anonymous subs are now supported in push_handlers, set_handlers, add_input_filter, etc. A fast cached cv is used with non-ithreaded perl. A slower deparse/eval approach (via B::Deparse) is used with 1.55 +27 -2 modperl-2.0/src/modules/perl/modperl_cmd.c Index: modperl_cmd.c =================================================================== RCS file: /home/cvs/modperl-2.0/src/modules/perl/modperl_cmd.c,v retrieving revision 1.54 retrieving revision 1.55 diff -u -r1.54 -r1.55 --- modperl_cmd.c 9 Feb 2004 18:18:16 -0000 1.54 +++ modperl_cmd.c 12 Feb 2004 02:05:28 -0000 1.55 @@ -241,17 +241,42 @@ modperl_config_dir_t *dcfg = (modperl_config_dir_t *)mconfig; const char *name = parms->cmd->name; + /* PerlSetVar and PerlAddVar logic. here's the deal... + * + * cfg->configvars holds the final PerlSetVar/PerlAddVar configuration + * for a given server or directory. however, getting to that point + * is kind of tricky, due to the add-style nature of PerlAddVar. + * + * the solution is to use cfg->setvars to hold PerlSetVar entries + * and cfg->addvars to hold PerlAddVar entries, each serving as a + * placeholder for when we need to know what's what in the merge routines. + * + * however, for the initial pass, apr_table_setn and apr_table_addn + * will properly build the configvars table, which will be visible to + * startup scripts trying to access per-server configurations. + * + * the end result is that we need to populate all three tables in order + * to keep things straight later on see merge_table_config_vars in + * modperl_config.c + */ modperl_table_modify_t func = strEQ(name, "PerlSetVar") ? apr_table_setn : apr_table_addn; - func(dcfg->vars, arg1, arg2); + apr_table_t *table = + strEQ(name, "PerlSetVar") ? dcfg->setvars : dcfg->addvars; + + func(table, arg1, arg2); + func(dcfg->configvars, arg1, arg2); MP_TRACE_d(MP_FUNC, "%s DIR: arg1 = %s, arg2 = %s\n", name, arg1, arg2); /* make available via Apache->server->dir_config */ if (!parms->path) { - func(scfg->vars, arg1, arg2); + table = strEQ(name, "PerlSetVar") ? scfg->setvars : scfg->addvars; + + func(table, arg1, arg2); + func(scfg->configvars, arg1, arg2); MP_TRACE_d(MP_FUNC, "%s SRV: arg1 = %s, arg2 = %s\n", name, arg1, arg2); 1.75 +97 -20 modperl-2.0/src/modules/perl/modperl_config.c Index: modperl_config.c =================================================================== RCS file: /home/cvs/modperl-2.0/src/modules/perl/modperl_config.c,v retrieving revision 1.74 retrieving revision 1.75 diff -u -r1.74 -r1.75 --- modperl_config.c 10 Jan 2004 02:52:20 -0000 1.74 +++ modperl_config.c 12 Feb 2004 02:05:28 -0000 1.75 @@ -17,24 +17,26 @@ #define merge_item(item) \ mrg->item = add->item ? add->item : base->item -/* take the 'base' values, and override with 'add' values if any */ static apr_table_t *modperl_table_overlap(apr_pool_t *p, apr_table_t *base, apr_table_t *add) { - int i; - const apr_array_header_t *arr = apr_table_elts(base); - apr_table_entry_t *entries = (apr_table_entry_t *)arr->elts; - apr_table_t *merge = apr_table_copy(p, add); - - for (i = 0; i < arr->nelts; i++) { - if (apr_table_get(add, entries[i].key)) { - continue; - } - else { - apr_table_addn(merge, entries[i].key, entries[i].val); - } - } + /* take the base (parent) values, and override with add (child) values, + * generating a new table. entries in add but not in base will be + * added to the new table. all using core apr table routines. + * + * note that this is equivalent to apr_table_overlap except a new + * table is generated, which is required (otherwise we would clobber + * the existing parent or child configurations) + */ + apr_table_t *merge = apr_table_overlay(p, base, add); + + /* compress will squash each key to the last value in the table. this + * is acceptable for all tables that expect only a single value per key + * such as PerlPassEnv and PerlSetEnv. PerlSetVar/PerlAddVar get their + * own, non-standard, merge routines in merge_table_config_vars. + */ + apr_table_compress(merge, APR_OVERLAP_TABLES_SET); return merge; } @@ -42,6 +44,53 @@ #define merge_table_overlap_item(item) \ mrg->item = modperl_table_overlap(p, base->item, add->item) +static apr_table_t *merge_table_config_vars(apr_pool_t *p, + apr_table_t *configvars, + apr_table_t *set, + apr_table_t *add) +{ + apr_table_t *base = apr_table_copy(p, configvars); + apr_table_t *merged_config_vars; + + const apr_array_header_t *arr; + apr_table_entry_t *entries; + int i; + + /* configvars already contains a properly merged PerlSetVar/PerlAddVar + * configuration for the base (parent), so all we need to do is merge + * the add (child) configuration into it properly. + * + * any PerlSetVar settings in the add (child) config need to reset + * existing entries in the base (parent) config, or generate a + * new entry where none existed previously. PerlAddVar settings + * are merged into that. + * + * unfortunately, there is no set of apr functions to do this for us - + * apr_compress_table would be ok, except it always merges mulit-valued + * keys into one, regardless of the merge flag, which is no good - we + * need separate entries, not a single comma-delimted entry. + * + * fortunately, the logic here is simple - first, (re)set the base (parent) + * table where a PerlSetVar entry exists in the child (add) configuration. + * then, just overlay the PerlAddVar configuration into it. + */ + + arr = apr_table_elts(set); + entries = (apr_table_entry_t *)arr->elts; + + /* hopefully this is faster than using apr_table_do */ + for (i = 0; i < arr->nelts; i++) { + apr_table_setn(base, entries[i].key, entries[i].val); + } + + /* at this point, all the PerlSetVar merging has happened. add in the + * add (child) PerlAddVar entries and we're done + */ + merged_config_vars = apr_table_overlay(p, base, add); + + return merged_config_vars; +} + #define merge_handlers(merge_flag, array) \ if (merge_flag(mrg)) { \ mrg->array = modperl_handler_array_merge(p, \ @@ -71,10 +120,22 @@ merge_item(location); - merge_table_overlap_item(vars); - merge_table_overlap_item(SetEnv); + /* this is where we merge PerlSetVar and PerlAddVar together */ + mrg->configvars = merge_table_config_vars(p, + base->configvars, + add->setvars, add->addvars); + + /* note we don't care about merging dcfg->setvars or dcfg->addvars + * specifically - what is important to merge is dfcg->configvars. + * but we need to keep track of the entries for this config, so + * the merged values are simply the values for the add (current) + * configuration. + */ + mrg->setvars = add->setvars; + mrg->addvars = add->addvars; + /* XXX: check if Perl*Handler is disabled */ for (i=0; i < MP_HANDLER_NUM_PER_DIR; i++) { merge_handlers(MpDirMERGE_HANDLERS, handlers_per_dir[i]); @@ -107,7 +168,9 @@ scfg->argv = apr_array_make(p, 2, sizeof(char *)); - scfg->vars = apr_table_make(p, 2); + scfg->setvars = apr_table_make(p, 2); + scfg->addvars = apr_table_make(p, 2); + scfg->configvars = apr_table_make(p, 2); scfg->PassEnv = apr_table_make(p, 2); scfg->SetEnv = apr_table_make(p, 2); @@ -130,7 +193,9 @@ dcfg->flags = modperl_options_new(p, MpDirType); - dcfg->vars = apr_table_make(p, 2); + dcfg->setvars = apr_table_make(p, 2); + dcfg->addvars = apr_table_make(p, 2); + dcfg->configvars = apr_table_make(p, 2); dcfg->SetEnv = apr_table_make(p, 2); @@ -224,11 +289,23 @@ merge_item(PerlModule); merge_item(PerlRequire); - merge_table_overlap_item(vars); - merge_table_overlap_item(SetEnv); merge_table_overlap_item(PassEnv); + /* this is where we merge PerlSetVar and PerlAddVar together */ + mrg->configvars = merge_table_config_vars(p, + base->configvars, + add->setvars, add->addvars); + + /* note we don't care about merging dcfg->setvars or dcfg->addvars + * specifically - what is important to merge is dfcg->configvars. + * but we need to keep track of the entries for this config, so + * the merged values are simply the values for the add (current) + * configuration. + */ + mrg->setvars = add->setvars; + mrg->addvars = add->addvars; + merge_item(threaded_mpm); merge_item(server); 1.73 +6 -2 modperl-2.0/src/modules/perl/modperl_types.h Index: modperl_types.h =================================================================== RCS file: /home/cvs/modperl-2.0/src/modules/perl/modperl_types.h,v retrieving revision 1.72 retrieving revision 1.73 diff -u -r1.72 -r1.73 --- modperl_types.h 9 Feb 2004 19:37:38 -0000 1.72 +++ modperl_types.h 12 Feb 2004 02:05:28 -0000 1.73 @@ -111,7 +111,9 @@ } modperl_interp_scope_e; typedef struct { - MpHV *vars; + MpHV *setvars; + MpHV *addvars; + MpHV *configvars; MpHV *SetEnv; MpHV *PassEnv; MpAV *PerlRequire, *PerlModule; @@ -142,7 +144,9 @@ char *PerlDispatchHandler; MpAV *handlers_per_dir[MP_HANDLER_NUM_PER_DIR]; MpHV *SetEnv; - MpHV *vars; + MpHV *setvars; + MpHV *addvars; + MpHV *configvars; modperl_options_t *flags; #ifdef USE_ITHREADS modperl_interp_scope_e interp_scope; 1.62 +2 -2 modperl-2.0/src/modules/perl/modperl_util.c Index: modperl_util.c =================================================================== RCS file: /home/cvs/modperl-2.0/src/modules/perl/modperl_util.c,v retrieving revision 1.61 retrieving revision 1.62 diff -u -r1.61 -r1.62 --- modperl_util.c 19 Jan 2004 19:59:58 -0000 1.61 +++ modperl_util.c 12 Feb 2004 02:05:28 -0000 1.62 @@ -568,7 +568,7 @@ if (r && r->per_dir_config) { MP_dDCFG; - retval = modperl_table_get_set(aTHX_ dcfg->vars, + retval = modperl_table_get_set(aTHX_ dcfg->configvars, key, sv_val, FALSE); } @@ -576,7 +576,7 @@ if (s && s->module_config) { MP_dSCFG(s); SvREFCNT_dec(retval); /* in case above did newSV(0) */ - retval = modperl_table_get_set(aTHX_ scfg->vars, + retval = modperl_table_get_set(aTHX_ scfg->configvars, key, sv_val, FALSE); } else { 1.1 modperl-2.0/t/htdocs/merge2/htaccess Index: htaccess =================================================================== # htaccess file for t/response/TestModperl/merge.pm PerlSetEnv MergeSetEnv3 SetEnv3Merge3Val PerlSetVar MergeSetVar3 SetVar3Merge3Val PerlSetVar MergeAddVar3 AddVar3Merge3Val1 PerlAddVar MergeAddVar3 AddVar3Merge3Val2 1.1 modperl-2.0/t/modperl/merge.t Index: merge.t =================================================================== use strict; use warnings FATAL => 'all'; use Apache::TestRequest qw(GET_BODY_ASSERT); use Apache::Test; use Apache::TestUtil; my $module = 'TestModperl::merge'; Apache::TestRequest::module($module); my $config = Apache::Test::config(); my $hostport = Apache::TestRequest::hostport($config); my $base = "http://$hostport"; # test server-to-container merging (without overrides) for: # PerlSetEnv # PerlPassEnv # PerlSetVar # PerlAddVar my $uri = "$base/merge"; t_debug("connecting to $uri"); print GET_BODY_ASSERT $uri; 1.1 modperl-2.0/t/modperl/merge2.t Index: merge2.t =================================================================== use strict; use warnings FATAL => 'all'; use Apache::TestRequest qw(GET_BODY_ASSERT); use Apache::Test; use Apache::TestUtil; my $module = 'TestModperl::merge'; Apache::TestRequest::module($module); my $config = Apache::Test::config(); my $hostport = Apache::TestRequest::hostport($config); my $base = "http://$hostport"; # test server-to-container merging (with overrides) for: # PerlSetEnv # PerlPassEnv # PerlSetVar # PerlAddVar my $uri = "$base/merge2/"; t_debug("connecting to $uri"); print GET_BODY_ASSERT $uri; 1.1 modperl-2.0/t/modperl/merge3.t Index: merge3.t =================================================================== use strict; use warnings FATAL => 'all'; use Apache::TestRequest qw(GET_BODY_ASSERT); use Apache::Test; use Apache::TestUtil; my $module = 'TestModperl::merge'; Apache::TestRequest::module($module); my $config = Apache::Test::config(); my $hostport = Apache::TestRequest::hostport($config); my $base = "http://$hostport"; # test multi-level merging (server-to-container-to-htaccess) for: # PerlSetEnv # PerlPassEnv # PerlSetVar # PerlAddVar my $uri = "$base/merge2/merge3.html"; t_debug("connecting to $uri"); print GET_BODY_ASSERT $uri; 1.1 modperl-2.0/t/response/TestModperl/merge.pm Index: merge.pm =================================================================== package TestModperl::merge; use strict; use warnings FATAL => 'all'; use Apache::Server (); use Apache::ServerUtil (); use Apache::RequestUtil (); use APR::Table (); use Apache::Test; use Apache::TestUtil; use Apache::Const -compile => 'OK'; # this is the configuration and handler for t/modperl/merge.t, # t/modperl/merge2.t, and t/modperl/merge3.t. see any of those # tests and/or the below configuration for more details # result tables for the below tests (trying to make the code more simple...) # the hash itself represents a request # the keys to the main hash represent merge levels - 1 for the non-overriding # merge, 2 for an overriding merge, and 3 for a two-level merge # the rest should be self-explanatory - settings and expected values. our %merge1 = ( 1 => { PerlPassEnv => [APACHE_TEST_HOSTTYPE => 'z80'], PerlSetEnv => [MergeSetEnv1 => 'SetEnv1Val'], PerlSetVar => [MergeSetVar1 => 'SetVar1Val'], PerlAddVar => [MergeAddVar1 => ['AddVar1Val1', 'AddVar1Val2']], }, 2 => { PerlSetEnv => [MergeSetEnv2 => 'SetEnv2Val'], PerlSetVar => [MergeSetVar2 => 'SetVar2Val'], PerlAddVar => [MergeAddVar2 => ['AddVar2Val1', 'AddVar2Val2']], }, 3 => { PerlSetEnv => [MergeSetEnv3 => 'SetEnv3Val'], PerlSetVar => [MergeSetVar3 => 'SetVar3Val'], PerlAddVar => [MergeAddVar3 => ['AddVar3Val1', 'AddVar3Val2']], }, ); our %merge2 = ( 1 => { PerlPassEnv => [APACHE_TEST_HOSTTYPE => 'z80'], PerlSetEnv => [MergeSetEnv1 => 'SetEnv1Val'], PerlSetVar => [MergeSetVar1 => 'SetVar1Val'], PerlAddVar => [MergeAddVar1 => ['AddVar1Val1', 'AddVar1Val2']], }, 2 => { PerlSetEnv => [MergeSetEnv2 => 'SetEnv2Merge2Val'], PerlSetVar => [MergeSetVar2 => 'SetVar2Merge2Val'], PerlAddVar => [MergeAddVar2 => ['AddVar2Merge2Val1', 'AddVar2Merge2Val2']], }, 3 => { PerlSetEnv => [MergeSetEnv3 => 'SetEnv3Val'], PerlSetVar => [MergeSetVar3 => 'SetVar3Val'], PerlAddVar => [MergeAddVar3 => ['AddVar3Val1', 'AddVar3Val2']], }, ); our %merge3 = ( 1 => { PerlPassEnv => [APACHE_TEST_HOSTTYPE => 'z80'], PerlSetEnv => [MergeSetEnv1 => 'SetEnv1Val'], PerlSetVar => [MergeSetVar1 => 'SetVar1Val'], PerlAddVar => [MergeAddVar1 => ['AddVar1Val1', 'AddVar1Val2']], }, 2 => { PerlSetEnv => [MergeSetEnv2 => 'SetEnv2Merge2Val'], PerlSetVar => [MergeSetVar2 => 'SetVar2Merge2Val'], PerlAddVar => [MergeAddVar2 => ['AddVar2Merge2Val1', 'AddVar2Merge2Val2']], }, 3 => { PerlSetEnv => [MergeSetEnv3 => 'SetEnv3Merge3Val'], PerlSetVar => [MergeSetVar3 => 'SetVar3Merge3Val'], PerlAddVar => [MergeAddVar3 => ['AddVar3Merge3Val1', 'AddVar3Merge3Val2']], }, ); sub handler { my $r = shift; plan $r, tests => 10; no strict qw(refs); my $location = $r->location; my $hash; if ($location =~ m/(merge3)/) { $hash = $1; } elsif ($location =~ m/(merge2)/) { $hash = $1; } else { $hash = 'merge1'; } t_debug("testing against results in $hash"); foreach my $level (sort keys %$hash) { foreach my $directive (sort keys %{$hash->{$level}}) { my $key = $hash->{$level}->{$directive}->[0]; my $value = $hash->{$level}->{$directive}->[1]; my @expected = ref $value ? @$value : $value; my $comment = join ' ', $directive, $key, @expected; if ($directive =~ m/Env/) { my $received = $ENV{$key}; ok t_cmp($expected[0], $received, $comment); } elsif ($directive =~ m/Set/) { my $received = $r->dir_config->get($key); ok t_cmp($expected[0], $received, $comment); } else { my @received = $r->dir_config->get($key); ok t_cmp([EMAIL PROTECTED], [EMAIL PROTECTED], $comment); } } } Apache::OK; } 1; __END__ <NoAutoConfig> PerlModule TestModperl::merge <VirtualHost TestModperl::merge> # these should pass through all merges untouched PerlPassEnv APACHE_TEST_HOSTTYPE PerlSetEnv MergeSetEnv1 SetEnv1Val PerlSetVar MergeSetVar1 SetVar1Val PerlSetVar MergeAddVar1 AddVar1Val1 PerlAddVar MergeAddVar1 AddVar1Val2 # these are overridden in /merge1 and /merge1/merge2 PerlSetEnv MergeSetEnv2 SetEnv2Val PerlSetVar MergeSetVar2 SetVar2Val PerlSetVar MergeAddVar2 AddVar2Val1 PerlAddVar MergeAddVar2 AddVar2Val2 # these are overridden in /merge1/merge2 via htaccess PerlSetEnv MergeSetEnv3 SetEnv3Val PerlSetVar MergeSetVar3 SetVar3Val PerlSetVar MergeAddVar3 AddVar3Val1 PerlAddVar MergeAddVar3 AddVar3Val2 <Location /merge> # same as per-server level SetHandler perl-script PerlResponseHandler TestModperl::merge </Location> AccessFileName htaccess <Directory @DocumentRoot@/merge2> # overrides "2" values - "1" and "3" values left untouched PerlSetEnv MergeSetEnv2 SetEnv2Merge2Val PerlSetVar MergeSetVar2 SetVar2Merge2Val PerlSetVar MergeAddVar2 AddVar2Merge2Val1 PerlAddVar MergeAddVar2 AddVar2Merge2Val2 SetHandler perl-script PerlResponseHandler TestModperl::merge # don't trigger htaccess files automatically AllowOverride none <Files merge3.html> # initiate a double merge with htaccess file AllowOverride all </Files> </Directory> </VirtualHost> </NoAutoConfig> 1.17 +0 -4 modperl-2.0/todo/release Index: release =================================================================== RCS file: /home/cvs/modperl-2.0/todo/release,v retrieving revision 1.16 retrieving revision 1.17 diff -u -r1.16 -r1.17 --- release 10 Feb 2004 15:02:00 -0000 1.16 +++ release 12 Feb 2004 02:05:29 -0000 1.17 @@ -84,10 +84,6 @@ release it any dependency on mod_perl will be resolved as mod_perl 2.0, when mod_perl 1.0 may be required instead. -* Set/Add overlapping in config which is not doing the right thing. See: - http://marc.theaimsgroup.com/?l=apache-modperl-dev&m=100622977803237&w=2 - http://marc.theaimsgroup.com/?t=97984528900002&r=1&w=2 - * Apache::{Server,Process} classes: require mutex lock for writing (e.g. $s->(error_fname|error_log) Status: most likely some server/process datastructures aren't