[openib-general] [PATCH 2/4] Modular routing engine (unicast only yet).
Sasha Khapyorsky
sashak at voltaire.com
Sat Jun 10 17:32:41 PDT 2006
This patch introduces routing_engine structure which may be used for
"plugging" new routing module. Currently only unicast callbacks are
supported (multicast can be added later). And existing routing module
is up-down 'updn', may be activated with '-R updn' option (instead of
old '-u'). General usage is:
$ opensm -R 'module-name'
Signed-off-by: Sasha Khapyorsky <sashak at voltaire.com>
---
osm/include/opensm/osm_opensm.h | 17 ++++++++-
osm/include/opensm/osm_subnet.h | 16 ++------
osm/include/opensm/osm_ucast_updn.h | 26 -------------
osm/opensm/main.c | 26 +++++--------
osm/opensm/osm_opensm.c | 41 ++++++++++++++++++---
osm/opensm/osm_subnet.c | 23 ++++++------
osm/opensm/osm_ucast_mgr.c | 69 ++++++++++++++++++++++++-----------
osm/opensm/osm_ucast_updn.c | 69 ++++++++++++++++++-----------------
8 files changed, 156 insertions(+), 131 deletions(-)
diff --git a/osm/include/opensm/osm_opensm.h b/osm/include/opensm/osm_opensm.h
index 3235ad4..3e6e120 100644
--- a/osm/include/opensm/osm_opensm.h
+++ b/osm/include/opensm/osm_opensm.h
@@ -92,6 +92,18 @@ BEGIN_C_DECLS
*
*********/
+/*
+ * routing engine structure - yet limited by ucast_fdb_assign and
+ * ucast_build_fwd_tables (multicast callbacks may be added later)
+ */
+struct osm_routing_engine {
+ const char *name;
+ void *context;
+ int (*ucast_build_fwd_tables)(void *context);
+ int (*ucast_fdb_assign)(void *context);
+ void (*delete)(void *context);
+};
+
/****s* OpenSM: OpenSM/osm_opensm_t
* NAME
* osm_opensm_t
@@ -116,7 +128,7 @@ typedef struct _osm_opensm_t
osm_log_t log;
cl_dispatcher_t disp;
cl_plock_t lock;
- updn_t *p_updn_ucast_routing;
+ struct osm_routing_engine routing_engine;
osm_stats_t stats;
} osm_opensm_t;
/*
@@ -153,6 +165,9 @@ typedef struct _osm_opensm_t
* lock
* Shared lock guarding most OpenSM structures.
*
+* routing_engine
+* Routing engine, will be initialized then used
+*
* stats
* Open SM statistics block
*
diff --git a/osm/include/opensm/osm_subnet.h b/osm/include/opensm/osm_subnet.h
index 4db449d..a637367 100644
--- a/osm/include/opensm/osm_subnet.h
+++ b/osm/include/opensm/osm_subnet.h
@@ -272,13 +272,11 @@ typedef struct _osm_subn_opt
uint32_t max_port_profile;
osm_pfn_ui_extension_t pfn_ui_pre_lid_assign;
void * ui_pre_lid_assign_ctx;
- osm_pfn_ui_extension_t pfn_ui_ucast_fdb_assign;
- void * ui_ucast_fdb_assign_ctx;
osm_pfn_ui_mcast_extension_t pfn_ui_mcast_fdb_assign;
void * ui_mcast_fdb_assign_ctx;
boolean_t sweep_on_trap;
osm_testability_modes_t testability_mode;
- boolean_t updn_activate;
+ char * routing_engine_name;
char * updn_guid_file;
boolean_t exit_on_fatal;
boolean_t honor_guid2lid_file;
@@ -407,13 +405,6 @@ typedef struct _osm_subn_opt
* ui_pre_lid_assign_ctx
* A UI context (void *) to be provided to the pfn_ui_pre_lid_assign
*
-* pfn_ui_ucast_fdb_assign
-* A UI function to be called instead of the ucast manager FDB
-* configuration.
-*
-* ui_ucast_fdb_assign_ctx
-* A UI context (void *) to be provided to the pfn_ui_ucast_fdb_assign
-*
* pfn_ui_mcast_fdb_assign
* A UI function to be called inside the mcast manager instead of the
* call for the build spanning tree. This will be called on every
@@ -429,9 +420,8 @@ typedef struct _osm_subn_opt
* testability_mode
* Object that indicates if we are running in a special testability mode.
*
-* updn_activate
-* Object that indicates if we are running the UPDN algorithm (TRUE) or
-* Min Hop Algorithm (FALSE)
+* routing_engine_name
+* Name of used routing engine (other than default Min Hop Algorithm)
*
* updn_guid_file
* Pointer to name of the UPDN guid file given by User
diff --git a/osm/include/opensm/osm_ucast_updn.h b/osm/include/opensm/osm_ucast_updn.h
index 027056c..fbf8782 100644
--- a/osm/include/opensm/osm_ucast_updn.h
+++ b/osm/include/opensm/osm_ucast_updn.h
@@ -421,32 +421,6 @@ osm_subn_calc_up_down_min_hop_table(
* This function returns 0 when rankning has succeded , otherwise 1.
******/
-/****f* OpenSM: OpenSM/osm_updn_reg_calc_min_hop_table
-* NAME
-* osm_updn_reg_calc_min_hop_table
-*
-* DESCRIPTION
-* Registration function to ucast routing manager (instead of
-* Min Hop Algorithm)
-*
-* SYNOPSIS
-*/
-int
-osm_updn_reg_calc_min_hop_table(
- IN updn_t * p_updn,
- IN osm_subn_opt_t* p_opt );
-/*
-* PARAMETERS
-*
-* RETURN VALUES
-* 0 - on success , 1 - on failure
-*
-* NOTES
-*
-* SEE ALSO
-* osm_subn_calc_up_down_min_hop_table
-*********/
-
/****** Osmsh: UpDown/osm_updn_find_root_nodes_by_min_hop
* NAME
* osm_updn_find_root_nodes_by_min_hop
diff --git a/osm/opensm/main.c b/osm/opensm/main.c
index 22591eb..c888ed4 100644
--- a/osm/opensm/main.c
+++ b/osm/opensm/main.c
@@ -60,7 +60,6 @@ #include <opensm/osm_opensm.h>
#include <complib/cl_types.h>
#include <complib/cl_debug.h>
#include <vendor/osm_vendor_api.h>
-#include <opensm/osm_ucast_updn.h>
#include <opensm/osm_console.h>
/********************************************************************
@@ -174,10 +173,10 @@ show_usage(void)
" may disrupt subnet traffic.\n"
" Without -r, OpenSM attempts to preserve existing\n"
" LID assignments resolving multiple use of same LID.\n\n");
- printf( "-u\n"
- "--updn\n"
- " This option activate UPDN algorithm instead of Min Hop\n"
- " algorithm (default).\n");
+ printf( "-R\n"
+ "--routing_engine <engine name>\n"
+ " This option choose routing engine instead of Min Hop\n"
+ " algorithm (default). Supported engines: updn\n");
printf ("-a\n"
"--add_guid_file <path to file>\n"
" Set the root nodes for the Up/Down routing algorithm\n"
@@ -524,7 +523,7 @@ #endif
boolean_t cache_options = FALSE;
char *ignore_guids_file_name = NULL;
uint32_t val;
- const char * const short_option = "i:f:ed:g:l:s:t:a:P:NQuvVhorcyx";
+ const char * const short_option = "i:f:ed:g:l:s:t:a:R:P:NQvVhorcyx";
/*
In the array below, the 2nd parameter specified the number
@@ -556,7 +555,7 @@ #endif
{ "reassign_lids", 0, NULL, 'r'},
{ "priority", 1, NULL, 'p'},
{ "smkey", 1, NULL, 'k'},
- { "updn", 0, NULL, 'u'},
+ { "routing_engine",1, NULL, 'R'},
{ "add_guid_file", 1, NULL, 'a'},
{ "cache-options", 0, NULL, 'c'},
{ "stay_on_fatal", 0, NULL, 'y'},
@@ -776,9 +775,9 @@ #endif
opt.sm_key = sm_key;
break;
- case 'u':
- opt.updn_activate = TRUE;
- printf(" Activate UPDN algorithm\n");
+ case 'R':
+ opt.routing_engine_name = optarg;
+ printf(" Activate \'%s\' routing engine\n", optarg);
break;
case 'a':
@@ -885,13 +884,6 @@ #endif
setup_signals();
osm_opensm_sweep( &osm );
- /* since osm_opensm_init get opt as RO we'll set the opt value with UI pfn here */
- /* Now do the registration */
- if (opt.updn_activate)
- if (osm_updn_reg_calc_min_hop_table(osm.p_updn_ucast_routing, &(osm.subn.opt))) {
- status = IB_ERROR;
- goto Exit;
- }
if( run_once_flag == TRUE )
{
diff --git a/osm/opensm/osm_opensm.c b/osm/opensm/osm_opensm.c
index 8c422b5..52f06da 100644
--- a/osm/opensm/osm_opensm.c
+++ b/osm/opensm/osm_opensm.c
@@ -68,6 +68,37 @@ #include <opensm/osm_subnet.h>
#include <opensm/osm_sm.h>
#include <opensm/osm_vl15intf.h>
+struct routing_engine_module {
+ const char *name;
+ int (*setup)(osm_opensm_t *p_osm);
+};
+
+extern int osm_ucast_updn_setup(osm_opensm_t *p_osm);
+
+const static struct routing_engine_module routing_modules[] = {
+ {"null", NULL},
+ {"updn", osm_ucast_updn_setup },
+ {}
+};
+
+static int setup_routing_engine(osm_opensm_t *p_osm, const char *name)
+{
+ const struct routing_engine_module *r;
+ for (r = routing_modules ; r->name && *r->name ; r++) {
+ if(!strcmp(r->name, name)) {
+ p_osm->routing_engine.name = r->name;
+ if (r->setup(p_osm))
+ break;
+ osm_log (&p_osm->log, OSM_LOG_DEBUG,
+ "opensm: setup_routing_engine: "
+ "\'%s\' routing engine set up.\n",
+ p_osm->routing_engine.name);
+ return 0;
+ }
+ }
+ return -1;
+}
+
/**********************************************************************
**********************************************************************/
void
@@ -118,7 +149,8 @@ osm_opensm_destroy(
cl_disp_shutdown( &p_osm->disp );
/* do the destruction in reverse order as init */
- updn_destroy( p_osm->p_updn_ucast_routing );
+ if (p_osm->routing_engine.delete)
+ p_osm->routing_engine.delete(p_osm->routing_engine.context);
osm_sa_destroy( &p_osm->sa );
osm_sm_destroy( &p_osm->sm );
osm_db_destroy( &p_osm->db );
@@ -252,11 +284,8 @@ #endif
if( status != IB_SUCCESS )
goto Exit;
- /* HACK - the UpDown manager should have been a part of the osm_sm_t */
- /* Init updn struct */
- p_osm->p_updn_ucast_routing = updn_construct( );
- status = updn_init( p_osm->p_updn_ucast_routing );
- if( status != IB_SUCCESS )
+ if( p_opt->routing_engine_name &&
+ setup_routing_engine(p_osm, p_opt->routing_engine_name))
goto Exit;
Exit:
diff --git a/osm/opensm/osm_subnet.c b/osm/opensm/osm_subnet.c
index 7c08556..27f97ab 100644
--- a/osm/opensm/osm_subnet.c
+++ b/osm/opensm/osm_subnet.c
@@ -484,13 +484,11 @@ osm_subn_set_default_opt(
p_opt->max_port_profile = 0xffffffff;
p_opt->pfn_ui_pre_lid_assign = NULL;
p_opt->ui_pre_lid_assign_ctx = NULL;
- p_opt->pfn_ui_ucast_fdb_assign = NULL;
- p_opt->ui_ucast_fdb_assign_ctx = NULL;
p_opt->pfn_ui_mcast_fdb_assign = NULL;
p_opt->ui_mcast_fdb_assign_ctx = NULL;
p_opt->sweep_on_trap = TRUE;
p_opt->testability_mode = OSM_TEST_MODE_NONE;
- p_opt->updn_activate = FALSE;
+ p_opt->routing_engine_name = NULL;
p_opt->updn_guid_file = NULL;
p_opt->exit_on_fatal = TRUE;
subn_set_default_qos_options(&p_opt->qos_options);
@@ -911,9 +909,9 @@ osm_subn_parse_conf_file(
"sweep_on_trap",
p_key, p_val, &p_opts->sweep_on_trap);
- __osm_subn_opts_unpack_boolean(
- "updn_activate",
- p_key, p_val, &p_opts->updn_activate);
+ __osm_subn_opts_unpack_charp(
+ "routing_engine",
+ p_key, p_val, &p_opts->routing_engine_name);
__osm_subn_opts_unpack_charp(
"log_file", p_key, p_val, &p_opts->log_file);
@@ -1089,12 +1087,13 @@ osm_subn_write_conf_file(
opts_file,
"#\n# ROUTING OPTIONS\n#\n"
"# If true do not count switches as link subscriptions\n"
- "port_profile_switch_nodes %s\n\n"
- "# Activate the Up/Down routing algorithm\n"
- "updn_activate %s\n\n",
- p_opts->port_profile_switch_nodes ? "TRUE" : "FALSE",
- p_opts->updn_activate ? "TRUE" : "FALSE"
- );
+ "port_profile_switch_nodes %s\n\n",
+ p_opts->port_profile_switch_nodes ? "TRUE" : "FALSE");
+ if (p_opts->routing_engine_name)
+ fprintf( opts_file,
+ "# Routing engine\n"
+ "routing_engine %s\n\n",
+ p_opts->routing_engine_name);
if (p_opts->updn_guid_file)
fprintf( opts_file,
"# The file holding the Up/Down root node guids\n"
diff --git a/osm/opensm/osm_ucast_mgr.c b/osm/opensm/osm_ucast_mgr.c
index cac7f9b..0c0d635 100644
--- a/osm/opensm/osm_ucast_mgr.c
+++ b/osm/opensm/osm_ucast_mgr.c
@@ -62,6 +62,7 @@ #include <opensm/osm_node.h>
#include <opensm/osm_switch.h>
#include <opensm/osm_helper.h>
#include <opensm/osm_msgdef.h>
+#include <opensm/osm_opensm.h>
#define LINE_LENGTH 256
@@ -269,7 +270,7 @@ osm_ucast_mgr_dump_ucast_routes(
strcat( p_mgr->p_report_buf, "yes" );
else
{
- if (p_mgr->p_subn->opt.pfn_ui_ucast_fdb_assign) {
+ if (p_mgr->p_subn->p_osm->routing_engine.ucast_fdb_assign) {
ui_ucast_fdb_assign_func_defined = TRUE;
} else {
ui_ucast_fdb_assign_func_defined = FALSE;
@@ -708,7 +709,7 @@ __osm_ucast_mgr_process_port(
node_guid = osm_node_get_node_guid(osm_switch_get_node_ptr( p_sw ) );
/* Flag to mark whether or not a ui ucast fdb assign function was given */
- if (p_mgr->p_subn->opt.pfn_ui_ucast_fdb_assign)
+ if (p_mgr->p_subn->p_osm->routing_engine.ucast_fdb_assign)
ui_ucast_fdb_assign_func_defined = TRUE;
else
ui_ucast_fdb_assign_func_defined = FALSE;
@@ -753,7 +754,7 @@ __osm_ucast_mgr_process_port(
/* Up/Down routing can cause unreachable routes between some
switches so we do not report that as an error in that case */
- if (!p_mgr->p_subn->opt.updn_activate)
+ if (!p_mgr->p_subn->p_osm->routing_engine.ucast_fdb_assign)
{
osm_log( p_mgr->p_log, OSM_LOG_ERROR,
"__osm_ucast_mgr_process_port: ERR 3A08: "
@@ -973,6 +974,18 @@ __osm_ucast_mgr_process_tbl(
/**********************************************************************
**********************************************************************/
static void
+__osm_ucast_mgr_set_table_cb(
+ IN cl_map_item_t* const p_map_item,
+ IN void* context )
+{
+ osm_switch_t* const p_sw = (osm_switch_t*)p_map_item;
+ osm_ucast_mgr_t* const p_mgr = (osm_ucast_mgr_t*)context;
+ __osm_ucast_mgr_set_table( p_mgr, p_sw );
+}
+
+/**********************************************************************
+ **********************************************************************/
+static void
__osm_ucast_mgr_process_neighbors(
IN cl_map_item_t* const p_map_item,
IN void* context )
@@ -1058,12 +1071,14 @@ osm_ucast_mgr_process(
{
uint32_t i;
uint32_t iteration_max;
+ struct osm_routing_engine *p_routing_eng;
osm_signal_t signal;
cl_qmap_t *p_sw_guid_tbl;
OSM_LOG_ENTER( p_mgr->p_log, osm_ucast_mgr_process );
p_sw_guid_tbl = &p_mgr->p_subn->sw_guid_tbl;
+ p_routing_eng = &p_mgr->p_subn->p_osm->routing_engine;
CL_PLOCK_EXCL_ACQUIRE( p_mgr->p_lock );
@@ -1129,6 +1144,14 @@ osm_ucast_mgr_process(
i
);
+ if (p_routing_eng->ucast_build_fwd_tables &&
+ p_routing_eng->ucast_build_fwd_tables(p_routing_eng->context) == 0)
+ {
+ cl_qmap_apply_func( p_sw_guid_tbl,
+ __osm_ucast_mgr_set_table_cb, p_mgr );
+ } /* fallback on the regular path in case of failures */
+ else
+ {
/*
This is the place where we can load pre-defined routes
into the switches fwd_tbl structures.
@@ -1136,32 +1159,34 @@ osm_ucast_mgr_process(
Later code will use these values if not configured for
re-assignment.
*/
- if (p_mgr->p_subn->opt.pfn_ui_ucast_fdb_assign)
- {
- if( osm_log_is_active( p_mgr->p_log, OSM_LOG_DEBUG ) )
+ if (p_routing_eng->ucast_fdb_assign)
{
- osm_log( p_mgr->p_log, OSM_LOG_DEBUG,
- "osm_ucast_mgr_process: "
- "Invoking UI function pfn_ui_ucast_fdb_assign\n");
- }
- p_mgr->p_subn->opt.pfn_ui_ucast_fdb_assign(p_mgr->p_subn->opt.ui_ucast_fdb_assign_ctx);
- } else {
+ if( osm_log_is_active( p_mgr->p_log, OSM_LOG_DEBUG ) )
+ {
+ osm_log( p_mgr->p_log, OSM_LOG_DEBUG,
+ "osm_ucast_mgr_process: "
+ "Invoking \'%s\' function ucast_fdb_assign\n",
+ p_routing_eng->name);
+ }
+ p_routing_eng->ucast_fdb_assign(p_routing_eng->context);
+ } else {
osm_log( p_mgr->p_log, OSM_LOG_DEBUG,
"osm_ucast_mgr_process: "
"UI pfn was not invoked\n");
- }
+ }
- osm_log(p_mgr->p_log, OSM_LOG_INFO,
- "osm_ucast_mgr_process: "
- "Min Hop Tables configured on all switches\n");
+ osm_log(p_mgr->p_log, OSM_LOG_INFO,
+ "osm_ucast_mgr_process: "
+ "Min Hop Tables configured on all switches\n");
- /*
- Now that the lid matrixes have been built, we can
- build and download the switch forwarding tables.
- */
+ /*
+ Now that the lid matrixes have been built, we can
+ build and download the switch forwarding tables.
+ */
- cl_qmap_apply_func( p_sw_guid_tbl,
- __osm_ucast_mgr_process_tbl, p_mgr );
+ cl_qmap_apply_func( p_sw_guid_tbl,
+ __osm_ucast_mgr_process_tbl, p_mgr );
+ }
/* dump fdb into file: */
if ( osm_log_is_active( p_mgr->p_log, OSM_LOG_ROUTING ) )
diff --git a/osm/opensm/osm_ucast_updn.c b/osm/opensm/osm_ucast_updn.c
index d80f7eb..8e36854 100644
--- a/osm/opensm/osm_ucast_updn.c
+++ b/osm/opensm/osm_ucast_updn.c
@@ -76,8 +76,9 @@ __updn_get_dir(IN uint8_t cur_rank,
IN uint64_t cur_guid,
IN uint64_t rem_guid)
{
- uint32_t i = 0, max_num_guids = osm.p_updn_ucast_routing->updn_ucast_reg_inputs.num_guids;
- uint64_t *p_guid = osm.p_updn_ucast_routing->updn_ucast_reg_inputs.guid_list;
+ updn_t *p_updn = osm.routing_engine.context;
+ uint32_t i = 0, max_num_guids = p_updn->updn_ucast_reg_inputs.num_guids;
+ uint64_t *p_guid = p_updn->updn_ucast_reg_inputs.guid_list;
boolean_t cur_is_root = FALSE , rem_is_root = FALSE;
/* HACK: comes to solve root nodes connection, in a classic subnet root nodes does not connect
@@ -540,7 +541,7 @@ updn_init(
p_updn->updn_ucast_reg_inputs.guid_list = NULL;
p_updn->auto_detect_root_nodes = FALSE;
/* Check if updn is activated , then fetch root nodes */
- if (osm.subn.opt.updn_activate)
+ if (osm.routing_engine.context)
{
/*
Check the source for root node list, if file parse it, otherwise
@@ -569,7 +570,7 @@ updn_init(
{
p_tmp = malloc(sizeof(uint64_t));
*p_tmp = strtoull(line, NULL, 16);
- cl_list_insert_tail(osm.p_updn_ucast_routing->p_root_nodes, p_tmp);
+ cl_list_insert_tail(p_updn->p_root_nodes, p_tmp);
}
}
else
@@ -588,8 +589,8 @@ updn_init(
"osm_opensm_init: "
"UPDN - Root nodes fetching by file %s\n",
osm.subn.opt.updn_guid_file);
- guid_iterator = cl_list_head(osm.p_updn_ucast_routing->p_root_nodes);
- while( guid_iterator != cl_list_end(osm.p_updn_ucast_routing->p_root_nodes) )
+ guid_iterator = cl_list_head(p_updn->p_root_nodes);
+ while( guid_iterator != cl_list_end(p_updn->p_root_nodes) )
{
osm_log( &osm.log, OSM_LOG_DEBUG,
"osm_opensm_init: "
@@ -600,7 +601,7 @@ updn_init(
}
else
{
- osm.p_updn_ucast_routing->auto_detect_root_nodes = TRUE;
+ p_updn->auto_detect_root_nodes = TRUE;
}
/* If auto mode detection reuired - will be executed in main b4 the assignment of UI Ucast */
}
@@ -985,33 +986,6 @@ void __osm_updn_convert_list2array(IN up
/**********************************************************************
**********************************************************************/
-/* Registration function to ucast routing manager (instead of
- Min Hop Algorithm) */
-int
-osm_updn_reg_calc_min_hop_table(
- IN updn_t * p_updn,
- IN osm_subn_opt_t* p_opt )
-{
- OSM_LOG_ENTER(&(osm.log), osm_updn_reg_calc_min_hop_table);
- /*
- If root nodes were supplied by the user - we need to convert into array
- otherwise, will be created & converted in callback function activation
- */
- if (!p_updn->auto_detect_root_nodes)
- {
- __osm_updn_convert_list2array(p_updn);
- }
- osm_log (&(osm.log), OSM_LOG_DEBUG,
- "osm_updn_reg_calc_min_hop_table: "
- "assigning ucast fdb UI function with updn callback\n");
- p_opt->pfn_ui_ucast_fdb_assign = __osm_updn_call;
- p_opt->ui_ucast_fdb_assign_ctx = (void *)p_updn;
- OSM_LOG_EXIT(&(osm.log));
- return 0;
-}
-
-/**********************************************************************
- **********************************************************************/
/* Find Root nodes automatically by Min Hop Table info */
int
osm_updn_find_root_nodes_by_min_hop( OUT updn_t * p_updn )
@@ -1210,3 +1184,30 @@ osm_updn_find_root_nodes_by_min_hop( OUT
OSM_LOG_EXIT(&(osm.log));
return 0;
}
+
+/**********************************************************************
+ **********************************************************************/
+
+static void __osm_updn_delete(void *context)
+{
+ updn_t *p_updn = context;
+ updn_destroy(p_updn);
+}
+
+int osm_ucast_updn_setup(osm_opensm_t *p_osm)
+{
+ updn_t *p_updn;
+ p_updn = updn_construct();
+ if (!p_updn)
+ return -1;
+ p_osm->routing_engine.context = p_updn;
+ p_osm->routing_engine.delete = __osm_updn_delete;
+ p_osm->routing_engine.ucast_fdb_assign = __osm_updn_call;
+
+ if (updn_init(p_updn) != IB_SUCCESS)
+ return -1;
+ if (!p_updn->auto_detect_root_nodes)
+ __osm_updn_convert_list2array(p_updn);
+
+ return 0;
+}
More information about the general
mailing list