I found useful in some cases to check the running configuration
parameters, particularly when more than one application/virtual host
is running on the same Apache installation. So I extended the
functionalities catered by the undocumented arrays RivetServerConf,
RivetDirConf and RivetUserConf and I got done a first implementation
for an introspection command of Rivet.
I named this command '::rivet::inspect', in order to make it distinctively
different from 'info', since a namespace import from ::rivet into
the global namespace would clash the 2 command definitions.
I don't have a special affection to this name, so I'm open to change it.
My first plan was to make it similar to PHP's phpinfo command, but I admit
I don't know of a straightforward way to store and return the plethora of
information (when meaningful to Rivet) that command caters. Anyway the
dictionary value returned by ::rivet::inspect can be extended to
new values or sections of configuration values.
::rivet::inspect has 3 forms:
1) with no arguments:
[::rivet::inspect] returns a dictionary with 3
subdictionaries having "server","user" and "dir" respectively for keys.
In this form '::rivet::inspect' returns only the configuration
parameters that were set by the admin or user in the conf files.
The dictionary works as a way to preserve compatibility
with the existing RivetServerConf,RivetDirConf and RivetUserConf arrays.
These arrays are maintained by Rivet_PropagateServerConfArrays and
Rivet_PropagatePerDirConfArrays functions in mod_rivet.c. Particularly
Rivet_PropagatePerDirConfArrays has a (though small) performance cost as
it gets run at every request.
Example: placing in BeforeScript the line
array set RivetDirConf [dict get [::rivet::inspect] dir]
would emulate what Rivet_PropagatePerDirConfArrays does, but only when
you need it
Bottom line: those Rivet_Propagate* function in mod_rivet.c can go now
and their arrays created on demand
2) ::rivet::inspect is called in this form:
::rivet::inspect -all
a dictionary is returned where each supported configuration keyword
(see below) is a key to a value read from the current configuration
record (as returned by Rivet_GetConf). Script values undefined are
filled with the string "<undef>"
3) ::rivet::inspect is given a single argument matching one of the
configuration keywords. The corresponding value is returned
Example:
[::rivet::inspect UploadMaxSize]
==> 2000000
Supported configuration keywords are "ServerInitScript",
"GlobalInitScript", "ChildInitScript", "ChildExitScript",
"BeforeScript", "AfterScript", "AfterEveryScript",
"AbortScript", "ErrorScript", "UploadMaxSize",
"UploadDirectory", "UploadFilesToVar", "SeparateVirtualInterps",
"HonorHeaderOnlyRequests"
This is the patch, including changes to mod_rivet.c to assure propagation
of some server configuration scripts.
In mod_rivet.c Rivet_UserConf and Rivet_DirConf are now restricted to
accept only configure scripts that are meaningful to them (child process
initialization and termination are handled at the server scope)
-- Massimo
Index: src/apache-2/Makefile.am
===================================================================
--- src/apache-2/Makefile.am (revision 1331892)
+++ src/apache-2/Makefile.am (working copy)
@@ -37,6 +37,7 @@
mod_rivet.c \
TclWebapache.c \
rivetCore.c \
+ rivetConf.c \
../rivetChannel.c \
../rivetParser.c
Index: src/apache-2/mod_rivet.c
===================================================================
--- src/apache-2/mod_rivet.c (revision 1331892)
+++ src/apache-2/mod_rivet.c (working copy)
@@ -697,6 +697,10 @@
rivet_server_conf *base, rivet_server_conf *add )
{
FILEDEBUGINFO;
+ new->rivet_child_init_script = add->rivet_child_init_script ?
+ add->rivet_child_init_script : base->rivet_child_init_script;
+ new->rivet_child_exit_script = add->rivet_child_exit_script ?
+ add->rivet_child_exit_script : base->rivet_child_exit_script;
new->rivet_before_script = add->rivet_before_script ?
add->rivet_before_script : base->rivet_before_script;
@@ -782,8 +786,8 @@
Tcl_IncrRefCount(rsc->rivet_default_error_script);
/* these are pointers so that they can be passed around... */
- rsc->cache_size = apr_pcalloc(p, sizeof(int));
- rsc->cache_free = apr_pcalloc(p, sizeof(int));
+ rsc->cache_size = apr_pcalloc(p, sizeof(int));
+ rsc->cache_free = apr_pcalloc(p, sizeof(int));
*(rsc->cache_size) = -1;
*(rsc->cache_free) = 0;
rsc->upload_max = RIVET_MAX_POST;
@@ -936,7 +940,7 @@
/* Create a global array with information about the server. */
Rivet_InitServerVariables(interp, p );
- Rivet_PropagateServerConfArray( interp, rsc );
+// Rivet_PropagateServerConfArray( interp, rsc );
/* Eval Rivet's init.tcl file to load in the Tcl-level commands. */
@@ -962,15 +966,9 @@
/* Loading into the interpreter the commands provided by librivet.so */
- /* It would be nice to have to whole set of Rivet commands
- * loaded into the interpreter at this stage. Unfortunately
- * a problem with the dynamic loader of some OS prevents us
- * from callingTcl_PkgRequire for 'rivetlib' because Apache segfaults
- * shortly after the extension library is loaded.
- * The problem was investigated on Linux and it became clear
- * that it's linked to the way Tcl calls dlopen (Bug #3216070)
- * The problem could be solved in Tcl8.6
- */
+ /* Bug #3216070 has been solved with 8.5.10 and commands shipped with
+ * Rivetlib can be mapped at this stage
+ */
if (Tcl_PkgRequire(interp, RIVETLIB_TCL_PACKAGE, "1.2", 1) == NULL)
{
@@ -1085,12 +1083,14 @@
*
* Command Arguments:
*
+ * RivetServerConf ServerInitScript <script>
* RivetServerConf GlobalInitScript <script>
* RivetServerConf ChildInitScript <script>
* RivetServerConf ChildExitScript <script>
* RivetServerConf BeforeScript <script>
* RivetServerConf AfterScript <script>
* RivetServerConf ErrorScript <script>
+ * RivetServerConf AfterEveryScript <script>
* RivetServerConf CacheSize <integer>
* RivetServerConf UploadDirectory <directory>
* RivetServerConf UploadMaxSize <integer>
@@ -1140,6 +1140,7 @@
* RivetDirConf BeforeScript <script>
* RivetDirConf AfterScript <script>
* RivetDirConf ErrorScript <script>
+ * RivetDirConf AfterEveryScript <script>
* RivetDirConf UploadDirectory <directory>
*/
@@ -1159,7 +1160,19 @@
if( STREQU( var, "UploadDirectory" ) ) {
rdc->upload_dir = val;
} else {
- string = Rivet_SetScript( cmd->pool, rdc, var, val );
+ if (STREQU(var,"BeforeScript") ||
+ STREQU(var,"AfterScript") ||
+ STREQU(var,"AbortScript") ||
+ STREQU(var,"AfterEveryScript") ||
+ STREQU(var,"ErrorScript"))
+ {
+ string = Rivet_SetScript( cmd->pool, rdc, var, val );
+ }
+ else
+ {
+ return apr_pstrcat(cmd->pool, "Rivet configuration error: '",var,
+ "' not valid for RivetDirConf", NULL);
+ }
}
if (string != NULL) apr_table_set( rdc->rivet_dir_vars, var, string );
@@ -1176,7 +1189,8 @@
*/
static const char *
-Rivet_UserConf( cmd_parms *cmd, void *vrdc,
+Rivet_UserConf( cmd_parms *cmd,
+ void *vrdc,
const char *var,
const char *val )
{
@@ -1192,9 +1206,23 @@
/* We have modified these scripts. */
/* This is less than ideal though, because it will get set to 1
* every time - FIXME. */
+
rdc->user_scripts_updated = 1;
- string = Rivet_SetScript( cmd->pool, rdc, var, val );
+ if (STREQU(var,"BeforeScript") ||
+ STREQU(var,"AfterScript") ||
+ STREQU(var,"AbortScript") ||
+ STREQU(var,"AfterEveryScript") ||
+ STREQU(var,"ErrorScript"))
+ {
+ string = Rivet_SetScript( cmd->pool, rdc, var, val );
+ }
+ else
+ {
+ return apr_pstrcat(cmd->pool, "Rivet configuration error: '",var,
+ "' not valid for RivetUserConf", NULL);
+ }
+
/* XXX Need to figure out what to do about setting the table. */
if (string != NULL) apr_table_set( rdc->rivet_user_vars, var, string );
return NULL;
@@ -1824,9 +1852,9 @@
}
#ifdef USE_APACHE_RSC
- Rivet_PropagatePerDirConfArrays( interp, rsc );
+// Rivet_PropagatePerDirConfArrays( interp, rsc );
#else
- Rivet_PropagatePerDirConfArrays( interp, rdc );
+// Rivet_PropagatePerDirConfArrays( interp, rdc );
#endif
/* Initialize this the first time through and keep it around. */
Index: src/apache-2/mod_rivet.h
===================================================================
--- src/apache-2/mod_rivet.h (revision 1331892)
+++ src/apache-2/mod_rivet.h (working copy)
@@ -131,5 +131,14 @@
#define RIVET_NEW_CONF(p) \
(rivet_server_conf *)apr_pcalloc(p, sizeof(rivet_server_conf))
+Tcl_Obj* Rivet_BuildConfDictionary ( Tcl_Interp* interp,
+ rivet_server_conf* rivet_conf);
+
+Tcl_Obj* Rivet_ReadConfParameter ( Tcl_Interp* interp,
+ rivet_server_conf* rivet_conf,
+ Tcl_Obj* par_name);
+
+Tcl_Obj* Rivet_CurrentConfDict ( Tcl_Interp* interp,
+ rivet_server_conf* rivet_conf);
#endif /* MOD_RIVET_H */
Index: src/apache-2/rivetConf.c
===================================================================
--- src/apache-2/rivetConf.c (revision 0)
+++ src/apache-2/rivetConf.c (revision 0)
@@ -0,0 +1,421 @@
+/*
+ * rivetConf.c - Functions for accessing Rivet configuration variables
+ *
+ * Functions in this file implement core function to be called mainly
+ * by the Rivet_InspectCmd function, which implments command 'inspect'
+ *
+ */
+
+/* Copyright 2002-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.
+*/
+
+/* $Id: */
+
+#include <tcl.h>
+#include <string.h>
+#include <apr_errno.h>
+#include <apr_strings.h>
+#include <apr_tables.h>
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_request.h"
+#include "http_core.h"
+#include "http_main.h"
+#include "util_script.h"
+#include "http_config.h"
+
+#include "mod_rivet.h"
+
+static const char* confDirectives[] =
+{
+ "ServerInitScript",
+ "GlobalInitScript",
+ "ChildInitScript",
+ "ChildExitScript",
+ "BeforeScript",
+ "AfterScript",
+ "AfterEveryScript",
+ "AbortScript",
+ "ErrorScript",
+ "UploadMaxSize",
+ "UploadDirectory",
+ "UploadFilesToVar",
+ "SeparateVirtualInterps",
+ "HonorHeaderOnlyRequests",
+ NULL
+};
+
+enum confIndices {
+ server_init_script,
+ global_init_script,
+ child_init_script,
+ child_exit_script,
+ before_script,
+ after_script,
+ after_every_script,
+ abort_script,
+ error_script,
+ upload_max,
+ upload_directory,
+ upload_files_to_var,
+ separate_virtual_interps,
+ honor_header_only_requests,
+ conf_index_terminator
+ };
+
+/*
+ * -- Rivet_ReadConfParameter
+ *
+ * This procedure reads a single field named par_name from
+ * rivet_server_conf structure and returns a Tcl_Obj pointer
+ * containing the field value. See confDirectives for a list
+ * of possible names. If the procedure is queried for a non
+ * existing field a NULL is returned.
+ *
+ * Arguments:
+ *
+ * - interp: pointer to the current Tcl interpreter structure
+ * - rsc: a pointer to a rivet_server_conf structure
+ * - par_name: parameter name (as listed in confDirectives)
+ *
+ * Returned value:
+ *
+ * - A Tcl_Obj pointer to the parameter value. Its refCount is
+ * set to 1
+ *
+ */
+
+Tcl_Obj*
+Rivet_ReadConfParameter ( Tcl_Interp* interp,
+ rivet_server_conf* rsc,
+ Tcl_Obj* par_name)
+{
+ int parameter_i;
+ Tcl_Obj* par_value;
+
+ if (Tcl_GetIndexFromObj(interp, par_name, confDirectives,
+ "<one of conf directives>", 0, ¶meter_i) == TCL_ERROR) {
+ return NULL;
+ }
+
+ switch (parameter_i)
+ {
+ case server_init_script:
+ {
+ par_value = rsc->rivet_server_init_script;
+ break;
+ }
+ case global_init_script:
+ {
+ par_value = rsc->rivet_global_init_script;
+ break;
+ }
+ case child_init_script:
+ {
+ par_value = rsc->rivet_child_init_script;
+ break;
+ }
+ case child_exit_script:
+ {
+ par_value = rsc->rivet_child_exit_script;
+ break;
+ }
+ case before_script:
+ {
+ par_value = rsc->rivet_before_script;
+ break;
+ }
+ case after_script:
+ {
+ par_value = rsc->rivet_after_script;
+ break;
+ }
+ case after_every_script:
+ {
+ par_value = rsc->after_every_script;
+ break;
+ }
+ case abort_script:
+ {
+ par_value = rsc->rivet_abort_script;
+ break;
+ }
+ case error_script:
+ {
+ par_value = rsc->rivet_error_script;
+ break;
+ }
+ case upload_max:
+ {
+ par_value = Tcl_NewIntObj(rsc->upload_max);
+ break;
+ }
+ case upload_directory:
+ {
+ par_value = Tcl_NewStringObj(rsc->upload_dir,-1);
+ break;
+ }
+ case upload_files_to_var:
+ {
+ par_value = Tcl_NewIntObj(rsc->upload_files_to_var);
+ break;
+ }
+ case separate_virtual_interps:
+ {
+ par_value = Tcl_NewIntObj(rsc->separate_virtual_interps);
+ break;
+ }
+ case honor_header_only_requests:
+ {
+ par_value = Tcl_NewIntObj(rsc->honor_header_only_reqs);
+ break;
+ }
+ default:
+ {
+ return NULL;
+ }
+ }
+
+ if (par_value == NULL)
+ {
+ par_value=Tcl_NewStringObj("<undef>",-1);
+ }
+
+ Tcl_IncrRefCount(par_value);
+
+ return par_value;
+}
+
+/*
+ * Rivet_ReadConfTable:
+ *
+ * This procedure builds a key-value list from an apr table
+ * It called from Rivet_BuildConfDictionary for Rivet configuration
+ * tables but it could work for every apr table
+ *
+ * Arguments:
+ *
+ * - interp: Tcl_Interp pointer
+ * - table: an apr_table_t pointer
+ */
+
+Tcl_Obj* Rivet_ReadConfTable ( Tcl_Interp* interp,
+ apr_table_t* table)
+{
+ Tcl_Obj* key;
+ Tcl_Obj* val;
+ apr_array_header_t *arr;
+ apr_table_entry_t *elts;
+ int nelts,i;
+ int tcl_status = TCL_OK;
+ Tcl_Obj* keyval_list = Tcl_NewObj();
+
+ Tcl_IncrRefCount(keyval_list);
+
+ arr = (apr_array_header_t*) apr_table_elts( table );
+ elts = (apr_table_entry_t *) arr->elts;
+ nelts = arr->nelts;
+
+/*
+ if (Tcl_IsShared(keyval_list))
+ {
+ fprintf(stderr,"building duplicate keyval_list\n");
+ keyval_list = Tcl_DuplicateObj(keyval_list);
+ }
+ */
+
+ for (i = 0; i < nelts; i++)
+ {
+ key = Tcl_NewStringObj( elts[i].key, -1);
+ val = Tcl_NewStringObj( elts[i].val, -1);
+ Tcl_IncrRefCount(key);
+ Tcl_IncrRefCount(val);
+
+ tcl_status = Tcl_ListObjAppendElement (interp,keyval_list,key);
+ if (tcl_status == TCL_ERROR)
+ {
+ Tcl_DecrRefCount(keyval_list);
+ Tcl_DecrRefCount(key);
+ Tcl_DecrRefCount(val);
+ return NULL;
+ }
+
+ tcl_status = Tcl_ListObjAppendElement (interp,keyval_list,val);
+ if (tcl_status == TCL_ERROR)
+ {
+ Tcl_DecrRefCount(keyval_list);
+ Tcl_DecrRefCount(key);
+ Tcl_DecrRefCount(val);
+ return NULL;
+ }
+
+ Tcl_DecrRefCount(key);
+ Tcl_DecrRefCount(val);
+ }
+
+ return keyval_list;
+}
+
+
+/*
+ * -- Rivet_BuildConfDictionary
+ *
+ * Parameters set in the configuration files are collected in three
+ * APR tables by Rivet_ServerConf,Rivet_DirConf and Rivet_UserConf.
+ *
+ * Arguments:
+ *
+ * - interp: Tcl_Interp pointer
+ * - rivet_conf: pointer to a rivet_server_conf structure as
+ * returned by Rivet_GetConf
+ *
+ * Returned value:
+ *
+ * - Tcl dictionary storing the dir/user/server configuration
+ *
+ */
+
+Tcl_Obj* Rivet_BuildConfDictionary ( Tcl_Interp* interp,
+ rivet_server_conf* rivet_conf)
+{
+ apr_table_t* conf_tables[3];
+ Tcl_Obj* keyval_list = NULL;
+ Tcl_Obj* key_list[2];
+ int it;
+ Tcl_Obj* conf_dict = Tcl_NewObj();
+
+ static const char* section_names[] =
+ {
+ "dir",
+ "user",
+ "server"
+ };
+
+ enum
+ {
+ dir_conf_section,
+ user_conf_section,
+ server_conf_section
+ };
+
+ conf_tables[0] = rivet_conf->rivet_dir_vars;
+ conf_tables[1] = rivet_conf->rivet_user_vars;
+ conf_tables[2] = rivet_conf->rivet_server_vars;
+
+ Tcl_IncrRefCount(conf_dict);
+
+ for (it=0; it < 3; it++)
+ {
+ keyval_list = Rivet_ReadConfTable(interp,conf_tables[it]);
+
+ if (keyval_list != NULL)
+ {
+ int i;
+ Tcl_Obj** objArrayPnt;
+ int objArrayCnt;
+ Tcl_Obj* val;
+
+ key_list[0] = Tcl_NewStringObj(section_names[it],-1);
+ Tcl_IncrRefCount(key_list[0]);
+
+ Tcl_ListObjGetElements(interp,keyval_list,&objArrayCnt,&objArrayPnt);
+ for (i=0; i < objArrayCnt; i+=2)
+ {
+ key_list[1] = objArrayPnt[i];
+ val = objArrayPnt[i+1];
+
+ Tcl_IncrRefCount(key_list[1]);
+ Tcl_IncrRefCount(val);
+
+ Tcl_DictObjPutKeyList(interp,conf_dict,2,key_list,val);
+
+ Tcl_DecrRefCount(key_list[1]);
+ Tcl_DecrRefCount(val);
+ }
+ Tcl_DecrRefCount(key_list[0]);
+ Tcl_DecrRefCount(keyval_list);
+ }
+ else
+ {
+ return NULL;
+ }
+ }
+
+ return conf_dict;
+}
+
+
+/*
+ * Rivet_CurrentConfDict
+ *
+ * This function is called by Rivet_InspectCmd which implements command
+ * '::rivet::inspect -all'. The function returns a dictionary where every
+ * parameter name (confDirectives) is associated to its value stored in
+ * the rivet_server_conf as returned by Rivet_GetConf
+ *
+ * Arguments:
+ *
+ * - interp: Tcl interpreter pointer
+ * - rivet_conf: a pointer to a rivet_server_conf structure
+ *
+ * Returned value_
+ *
+ * - a Tcl_Obj* pointer to a dictionary. The dictionary object
+ * refCount is set to 1
+ */
+
+Tcl_Obj* Rivet_CurrentConfDict ( Tcl_Interp* interp,
+ rivet_server_conf* rivet_conf)
+{
+ Tcl_Obj* dictObj = Tcl_NewObj();
+ Tcl_Obj* par_name;
+ static const char** p;
+
+ Tcl_IncrRefCount(dictObj);
+
+ for (p = confDirectives; (*p) != NULL; p++)
+ {
+ Tcl_Obj* par_value;
+
+ par_name = Tcl_NewStringObj(*p,-1);
+ Tcl_IncrRefCount(par_name);
+
+ par_value = Rivet_ReadConfParameter(interp,rivet_conf,par_name);
+ if (par_value != NULL)
+ {
+ Tcl_DictObjPut(interp,dictObj,par_name,par_value);
+ Tcl_DecrRefCount(par_value);
+ }
+ else
+ {
+ Tcl_Obj* message = Tcl_NewStringObj("Invalid configuration option: ",-1);
+
+ Tcl_IncrRefCount(message);
+ Tcl_AppendObjToObj(message,par_name);
+ Tcl_AddErrorInfo(interp, Tcl_GetStringFromObj(message,NULL));
+
+ Tcl_DecrRefCount(message);
+ Tcl_DecrRefCount(par_name);
+ Tcl_DecrRefCount(dictObj);
+ dictObj = NULL;
+ break;
+ }
+ Tcl_DecrRefCount(par_name);
+
+ }
+
+ return dictObj;
+}
Index: src/apache-2/rivetCore.c
===================================================================
--- src/apache-2/rivetCore.c (revision 1331892)
+++ src/apache-2/rivetCore.c (working copy)
@@ -1230,6 +1230,84 @@
/*
*-----------------------------------------------------------------------------
*
+ * Rivet_Inspect --
+ *
+ * Rivet configuration introspection. Command '::rivet::inspect'
+ * returns a dictionary of configuration data:
+ *
+ * Results:
+ * A dictionary or parameter value
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+TCL_CMD_HEADER( Rivet_InspectCmd )
+{
+ rivet_interp_globals* globals = Tcl_GetAssocData( interp, "rivet", NULL );
+ rivet_server_conf* rsc = Rivet_GetConf(globals->r);
+ int status = TCL_OK;
+
+ if (objc == 1)
+ {
+ Tcl_Obj* dictObj;
+
+ dictObj = Rivet_BuildConfDictionary(interp,rsc);
+ if (dictObj != NULL) {
+ Tcl_SetObjResult(interp,dictObj);
+ Tcl_DecrRefCount(dictObj);
+ } else {
+ status = TCL_ERROR;
+ }
+ }
+ else if (objc == 2)
+ {
+ Tcl_Obj* par_name = objv[1];
+
+ Tcl_IncrRefCount(par_name);
+ if (STRNEQU(Tcl_GetStringFromObj(par_name,NULL),"-all"))
+ {
+ Tcl_Obj* dictObj;
+
+ dictObj = Rivet_CurrentConfDict(interp,rsc);
+ if (dictObj == NULL)
+ {
+ status = TCL_ERROR;
+ }
+ else
+ {
+ Tcl_SetObjResult(interp,dictObj);
+ Tcl_DecrRefCount(dictObj);
+ }
+ }
+ else
+ {
+ Tcl_Obj* par_value = NULL;
+
+ par_value = Rivet_ReadConfParameter(interp,rsc,par_name);
+ if (par_value == NULL)
+ {
+ status = TCL_ERROR;
+ }
+ else
+ {
+ Tcl_SetObjResult(interp,par_value);
+ Tcl_DecrRefCount(par_value);
+ }
+ }
+
+ Tcl_DecrRefCount(par_name);
+ }
+ else
+ {
+ Tcl_WrongNumArgs( interp, 1, objv, "?server | dir | user? ?parameter name?" );
+ status = TCL_ERROR;
+ }
+ return status;
+}
+
+/*
+ *-----------------------------------------------------------------------------
+ *
* Rivet_LogError --
*
* Log an error from Rivet
@@ -1421,6 +1499,7 @@
RIVET_OBJ_CMD ("no_body",Rivet_NoBody);
RIVET_OBJ_CMD ("env",Rivet_EnvCmd);
RIVET_OBJ_CMD ("apache_log_error",Rivet_LogErrorCmd);
+ RIVET_OBJ_CMD ("inspect",Rivet_InspectCmd);
#ifdef TESTPANIC
RIVET_OBJ_CMD ("testpanic",TestpanicCmd);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]