Repository: qpid-dispatch Updated Branches: refs/heads/tross-DISPATCH-179-1 2aa0e26a7 -> c8f2c9df1
DISPATCH-179 - Implemented auto-link activation, added deprecation warnings to old configs. Project: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/repo Commit: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/commit/c8f2c9df Tree: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/tree/c8f2c9df Diff: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/diff/c8f2c9df Branch: refs/heads/tross-DISPATCH-179-1 Commit: c8f2c9df1b16a3f87991f049aed0f164477809c6 Parents: 2aa0e26 Author: Ted Ross <[email protected]> Authored: Wed Mar 16 17:16:30 2016 -0400 Committer: Ted Ross <[email protected]> Committed: Wed Mar 16 17:16:30 2016 -0400 ---------------------------------------------------------------------- python/qpid_dispatch/management/qdrouter.json | 12 ++- src/router_config.c | 22 ++++- src/router_core/agent_config_auto_link.c | 32 ++++++- src/router_core/agent_config_auto_link.h | 2 +- src/router_core/connections.c | 70 +++++++++----- src/router_core/route_control.c | 102 ++++++++++++++++++--- src/router_core/route_control.h | 14 +-- src/router_core/router_core_private.h | 20 ++++ src/router_node.c | 3 + 9 files changed, 229 insertions(+), 48 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/c8f2c9df/python/qpid_dispatch/management/qdrouter.json ---------------------------------------------------------------------- diff --git a/python/qpid_dispatch/management/qdrouter.json b/python/qpid_dispatch/management/qdrouter.json index 61d3642..6f70fe9 100644 --- a/python/qpid_dispatch/management/qdrouter.json +++ b/python/qpid_dispatch/management/qdrouter.json @@ -945,7 +945,17 @@ }, "linkRef": { "type": "string", - "description": "Reference to the org.apache.qpid.dispatch.router.link if the link is attached", + "description": "Reference to the org.apache.qpid.dispatch.router.link if the link exists", + "create": false + }, + "operStatus": { + "type": ["inactive", "attaching", "failed", "active", "quiescing", "idle"], + "description": "The operational status of this autoLink: inactive - The remote container is not connected; attaching - the link is attaching to the remote node; failed - the link attach failed; active - the link is attached and operational; quiescing - the link is transitioning to idle state; idle - the link is attached but there are no deliveries flowing and no unsettled deliveries.", + "create": false + }, + "lastError": { + "type": "string", + "description": "The error description from the last attach failure", "create": false } } http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/c8f2c9df/src/router_config.c ---------------------------------------------------------------------- diff --git a/src/router_config.c b/src/router_config.c index 202ee9f..c6963e8 100644 --- a/src/router_config.c +++ b/src/router_config.c @@ -29,6 +29,12 @@ qd_error_t qd_router_configure_fixed_address(qd_router_t *router, qd_entity_t *entity) { + static bool deprecate_warning = true; + if (deprecate_warning) { + deprecate_warning = false; + qd_log(router->log_source, QD_LOG_WARNING, "fixedAddress configuration is deprecated, switch to using address instead."); + } + qd_error_clear(); int phase = qd_entity_opt_long(entity, "phase", -1); QD_ERROR_RET(); qd_schema_fixedAddress_fanout_t fanout = qd_entity_get_long(entity, "fanout"); QD_ERROR_RET(); @@ -63,7 +69,7 @@ qd_error_t qd_router_configure_fixed_address(qd_router_t *router, qd_entity_t *e } // - // Formulate this configuration as a router.route and create it through the core management API. + // Formulate this configuration as a router.config.address and create it through the core management API. // qd_composed_field_t *body = qd_compose_subfield(0); qd_compose_start_map(body); @@ -97,6 +103,12 @@ qd_error_t qd_router_configure_fixed_address(qd_router_t *router, qd_entity_t *e qd_error_t qd_router_configure_waypoint(qd_router_t *router, qd_entity_t *entity) { + static bool deprecate_warning = true; + if (deprecate_warning) { + deprecate_warning = false; + qd_log(router->log_source, QD_LOG_WARNING, "waypoint configuration is deprecated, switch to using autoLink instead."); + } + /* char *address = qd_entity_get_string(entity, "address"); QD_ERROR_RET(); char *connector = qd_entity_get_string(entity, "connector"); QD_ERROR_RET(); @@ -131,7 +143,7 @@ qd_error_t qd_router_configure_waypoint(qd_router_t *router, qd_entity_t *entity static void qd_router_add_link_route(qdr_core_t *core, const char *prefix, const char *connector, const char* dir) { // - // Formulate this configuration as a router.route and create it through the core management API. + // Formulate this configuration as a router.config.linkRoute and create it through the core management API. // qd_composed_field_t *body = qd_compose_subfield(0); qd_compose_start_map(body); @@ -169,6 +181,12 @@ static void qd_router_add_link_route(qdr_core_t *core, const char *prefix, const qd_error_t qd_router_configure_lrp(qd_router_t *router, qd_entity_t *entity) { + static bool deprecate_warning = true; + if (deprecate_warning) { + deprecate_warning = false; + qd_log(router->log_source, QD_LOG_WARNING, "linkRoutePrefix configuration is deprecated, switch to using linkRoute instead."); + } + char *prefix = qd_entity_get_string(entity, "prefix"); QD_ERROR_RET(); char *connector = qd_entity_get_string(entity, "connector"); QD_ERROR_RET(); char *direction = qd_entity_get_string(entity, "dir"); QD_ERROR_RET(); http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/c8f2c9df/src/router_core/agent_config_auto_link.c ---------------------------------------------------------------------- diff --git a/src/router_core/agent_config_auto_link.c b/src/router_core/agent_config_auto_link.c index 6f8ef8a..b18fb90 100644 --- a/src/router_core/agent_config_auto_link.c +++ b/src/router_core/agent_config_auto_link.c @@ -31,6 +31,8 @@ #define QDR_CONFIG_AUTO_LINK_CONNECTION 6 #define QDR_CONFIG_AUTO_LINK_CONTAINER_ID 7 #define QDR_CONFIG_AUTO_LINK_LINK_REF 8 +#define QDR_CONFIG_AUTO_LINK_OPER_STATUS 9 +#define QDR_CONFIG_AUTO_LINK_LAST_ERROR 10 const char *qdr_config_auto_link_columns[] = {"name", @@ -39,9 +41,11 @@ const char *qdr_config_auto_link_columns[] = "addr", "dir", "phase", - "containerId", "connection", + "containerId", "linkRef", + "operStatus", + "lastError", 0}; @@ -110,6 +114,30 @@ static void qdr_config_auto_link_insert_column_CT(qdr_auto_link_t *al, int col, qd_compose_insert_string(body, id_str); } else qd_compose_insert_null(body); + break; + + case QDR_CONFIG_AUTO_LINK_OPER_STATUS: + switch (al->state) { + case QDR_AUTO_LINK_STATE_INACTIVE: text = "inactive"; break; + case QDR_AUTO_LINK_STATE_ATTACHING: text = "attaching"; break; + case QDR_AUTO_LINK_STATE_FAILED: text = "failed"; break; + case QDR_AUTO_LINK_STATE_ACTIVE: text = "active"; break; + case QDR_AUTO_LINK_STATE_QUIESCING: text = "quiescing"; break; + case QDR_AUTO_LINK_STATE_IDLE: text = "idle"; break; + } + + if (text) + qd_compose_insert_string(body, text); + else + qd_compose_insert_null(body); + break; + + case QDR_CONFIG_AUTO_LINK_LAST_ERROR: + if (al->last_error) + qd_compose_insert_string(body, al->last_error); + else + qd_compose_insert_null(body); + break; } } @@ -362,7 +390,7 @@ void qdra_config_auto_link_create_CT(qdr_core_t *core, bool is_container = !!container_field; qd_parsed_field_t *in_use_conn = is_container ? container_field : connection_field; - qdr_route_add_auto_link_CT(core, name, addr_field, dir, phase, in_use_conn, is_container); + al = qdr_route_add_auto_link_CT(core, name, addr_field, dir, phase, in_use_conn, is_container); // // Compose the result map for the response. http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/c8f2c9df/src/router_core/agent_config_auto_link.h ---------------------------------------------------------------------- diff --git a/src/router_core/agent_config_auto_link.h b/src/router_core/agent_config_auto_link.h index 578d402..e1a433f 100644 --- a/src/router_core/agent_config_auto_link.h +++ b/src/router_core/agent_config_auto_link.h @@ -28,7 +28,7 @@ void qdra_config_auto_link_update_CT(qdr_core_t *core, qdr_query_t *query, qd_pa void qdra_config_auto_link_delete_CT(qdr_core_t *core, qdr_query_t *query, qd_field_iterator_t *name, qd_field_iterator_t *identity); -#define QDR_CONFIG_AUTO_LINK_COLUMN_COUNT 9 +#define QDR_CONFIG_AUTO_LINK_COLUMN_COUNT 11 const char *qdr_config_auto_link_columns[QDR_CONFIG_AUTO_LINK_COLUMN_COUNT + 1]; http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/c8f2c9df/src/router_core/connections.c ---------------------------------------------------------------------- diff --git a/src/router_core/connections.c b/src/router_core/connections.c index 45d7920..fbce669 100644 --- a/src/router_core/connections.c +++ b/src/router_core/connections.c @@ -434,12 +434,12 @@ static void qdr_link_cleanup_CT(qdr_core_t *core, qdr_connection_t *conn, qdr_li } -static qdr_link_t *qdr_create_link_CT(qdr_core_t *core, - qdr_connection_t *conn, - qd_link_type_t link_type, - qd_direction_t dir, - qdr_terminus_t *source, - qdr_terminus_t *target) +qdr_link_t *qdr_create_link_CT(qdr_core_t *core, + qdr_connection_t *conn, + qd_link_type_t link_type, + qd_direction_t dir, + qdr_terminus_t *source, + qdr_terminus_t *target) { // // Create a new link, initiated by the router core. This will involve issuing a first-attach outbound. @@ -530,7 +530,7 @@ static char qdr_prefix_for_dir(qd_direction_t dir) } -static qd_address_treatment_t qdr_treatment_for_address_CT(qdr_core_t *core, qd_field_iterator_t *iter) +qd_address_treatment_t qdr_treatment_for_address_CT(qdr_core_t *core, qd_field_iterator_t *iter) { qdr_address_config_t *addr = 0; @@ -985,6 +985,17 @@ static void qdr_link_inbound_second_attach_CT(qdr_core_t *core, qdr_action_t *ac qdr_link_issue_credit_CT(core, link, link->capacity); switch (link->link_type) { case QD_LINK_ENDPOINT: + if (link->auto_link) { + // + // This second-attach is the completion of an auto-link. If the attach + // has a valid source, transition the auto-link to the "active" state. + // + if (qdr_terminus_get_address(source)) { + link->auto_link->state = QDR_AUTO_LINK_STATE_ACTIVE; + qdr_add_link_ref(&link->auto_link->addr->inlinks, link, QDR_LINK_LIST_CLASS_ADDRESS); + link->owning_addr = link->auto_link->addr; + } + } break; case QD_LINK_WAYPOINT: @@ -1002,6 +1013,22 @@ static void qdr_link_inbound_second_attach_CT(qdr_core_t *core, qdr_action_t *ac // switch (link->link_type) { case QD_LINK_ENDPOINT: + if (link->auto_link) { + // + // This second-attach is the completion of an auto-link. If the attach + // has a valid target, transition the auto-link to the "active" state. + // + if (qdr_terminus_get_address(target)) { + link->auto_link->state = QDR_AUTO_LINK_STATE_ACTIVE; + qdr_add_link_ref(&link->auto_link->addr->rlinks, link, QDR_LINK_LIST_CLASS_ADDRESS); + link->owning_addr = link->auto_link->addr; + if (DEQ_SIZE(link->auto_link->addr->rlinks) == 1) { + const char *key = (const char*) qd_hash_key_by_handle(link->auto_link->addr->hash_handle); + if (key && *key == 'M') + qdr_post_mobile_added_CT(core, key); + } + } + } break; case QD_LINK_WAYPOINT: @@ -1055,13 +1082,21 @@ static void qdr_link_inbound_detach_CT(qdr_core_t *core, qdr_action_t *action, b link->detach_count++; // - // TODO - For routed links, propagate the detach + // For routed links, propagate the detach // if (link->connected_link) { qdr_link_outbound_detach_CT(core, link->connected_link, error, QDR_CONDITION_NONE); return; } + // + // For auto links, switch the auto link to failed state and record the error + // + if (link->auto_link) { + link->auto_link->state = QDR_AUTO_LINK_STATE_FAILED; + // TODO - last_error + } + link->owning_addr = 0; if (link->link_direction == QD_INCOMING) { @@ -1110,6 +1145,10 @@ static void qdr_link_inbound_detach_CT(qdr_core_t *core, qdr_action_t *action, b } } + // + // TODO - If this link is owned by an auto_link, handle the unexpected detach. + // + if (link->detach_count == 1) { // // If the detach occurred via protocol, send a detach back. @@ -1127,21 +1166,6 @@ static void qdr_link_inbound_detach_CT(qdr_core_t *core, qdr_action_t *action, b // if (addr) qdr_check_addr_CT(core, addr, was_local); - - // - // Cases to be handled: - // - // Link is link-routed: - // Propagate the detach along the link-chain - // Link is half-detached and not link-routed: - // Issue a detach back to the originating node - // Link is fully detached: - // Free the qdr_link object - // Remove any address linkages associated with this link - // If the last dest for a local address is lost, notify the router (mobile_removed) - // Link is a router-control link: - // Issue a link-lost indication to the router - // } http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/c8f2c9df/src/router_core/route_control.c ---------------------------------------------------------------------- diff --git a/src/router_core/route_control.c b/src/router_core/route_control.c index 88a1e93..54f4573 100644 --- a/src/router_core/route_control.c +++ b/src/router_core/route_control.c @@ -69,7 +69,7 @@ static void qdr_route_log_CT(qdr_core_t *core, const char *text, const char *nam snprintf(id_string, 64, "%ld", id); qd_log(core->log, QD_LOG_INFO, "%s '%s' on %s %s", - log_name, text, key[0] == 'L' ? "connection" : "container", &key[1]); + text, log_name, key[0] == 'L' ? "connection" : "container", &key[1]); } @@ -77,7 +77,7 @@ static void qdr_link_route_activate_CT(qdr_core_t *core, qdr_link_route_t *lr, q { const char *key; - qdr_route_log_CT(core, "Activated Link Route", lr->name, lr->identity, conn); + qdr_route_log_CT(core, "Link Route Activated", lr->name, lr->identity, conn); // // Activate the address for link-routed destinations. If this is the first @@ -98,7 +98,7 @@ static void qdr_link_route_deactivate_CT(qdr_core_t *core, qdr_link_route_t *lr, { const char *key; - qdr_route_log_CT(core, "Deactivated Link Route", lr->name, lr->identity, conn); + qdr_route_log_CT(core, "Link Route Deactivated", lr->name, lr->identity, conn); // // Deactivate the address(es) for link-routed destinations. @@ -114,6 +114,42 @@ static void qdr_link_route_deactivate_CT(qdr_core_t *core, qdr_link_route_t *lr, } +static void qdr_auto_link_activate_CT(qdr_core_t *core, qdr_auto_link_t *al, qdr_connection_t *conn) +{ + const char *key; + + qdr_route_log_CT(core, "Auto Link Activated", al->name, al->identity, conn); + + // + // Activate the link for an auto_link. If this is the first activation for this + // address, notify the router module of the added address. + // + if (al->addr) { + qdr_terminus_t *source = 0; + qdr_terminus_t *target = 0; + qdr_terminus_t *term = qdr_terminus(0); + + if (al->dir == QD_INCOMING) + source = term; + else + target = term; + + key = (const char*) qd_hash_key_by_handle(al->addr->hash_handle); + if (key) { + qdr_terminus_set_address(term, &key[2]); // truncate the "Mp" annotation (where p = phase) + al->link = qdr_create_link_CT(core, conn, QD_LINK_ENDPOINT, al->dir, source, target); + al->link->auto_link = al; + al->state = QDR_AUTO_LINK_STATE_ATTACHING; + } + } +} + + +static void qdr_auto_link_deactivate_CT(qdr_core_t *core, qdr_auto_link_t *al, qdr_connection_t *conn) +{ +} + + void qdr_route_add_link_route_CT(qdr_core_t *core, qd_field_iterator_t *name, qd_parsed_field_t *prefix_field, @@ -169,14 +205,56 @@ void qdr_route_del_link_route_CT(qdr_core_t *core, qdr_link_route_t *lr) } -void qdr_route_add_auto_link_CT(qdr_core_t *core, - qd_field_iterator_t *name, - qd_parsed_field_t *addr_field, - qd_direction_t dir, - int phase, - qd_parsed_field_t *conn_id, - bool is_container) +qdr_auto_link_t *qdr_route_add_auto_link_CT(qdr_core_t *core, + qd_field_iterator_t *name, + qd_parsed_field_t *addr_field, + qd_direction_t dir, + int phase, + qd_parsed_field_t *conn_id, + bool is_container) { + qdr_auto_link_t *al = new_qdr_auto_link_t(); + + // + // Set up the link_route structure + // + ZERO(al); + al->identity = qdr_identifier(core); + al->name = name ? (char*) qd_field_iterator_copy(name) : 0; + al->dir = dir; + al->phase = phase; + al->state = QDR_AUTO_LINK_STATE_INACTIVE; + + // + // Find or create an address for the auto_link destination + // + qd_field_iterator_t *iter = qd_parse_raw(addr_field); + qd_address_iterator_reset_view(iter, ITER_VIEW_ADDRESS_HASH); + qd_address_iterator_set_phase(iter, (char) phase + '0'); + + qd_hash_retrieve(core->addr_hash, iter, (void*) &al->addr); + if (!al->addr) { + al->addr = qdr_address_CT(core, qdr_treatment_for_address_CT(core, iter)); + DEQ_INSERT_TAIL(core->addrs, al->addr); + qd_hash_insert(core->addr_hash, iter, al->addr, &al->addr->hash_handle); + } + + // + // Find or create a connection identifier structure for this auto_link + // + if (conn_id) { + al->conn_id = qdr_route_declare_id_CT(core, qd_parse_raw(conn_id), is_container); + DEQ_INSERT_TAIL_N(REF, al->conn_id->auto_link_refs, al); + if (al->conn_id->open_connection) + qdr_auto_link_activate_CT(core, al, al->conn_id->open_connection); + } + + // + // Add the auto_link to the core list + // + DEQ_INSERT_TAIL(core->auto_links, al); + + return al; } @@ -213,7 +291,7 @@ void qdr_route_connection_opened_CT(qdr_core_t *core, // qdr_auto_link_t *al = DEQ_HEAD(cid->auto_link_refs); while (al) { - //qdr_link_route_activate_CT(core, lr, conn); + qdr_auto_link_activate_CT(core, al, conn); al = DEQ_NEXT_N(REF, al); } } @@ -240,7 +318,7 @@ void qdr_route_connection_closed_CT(qdr_core_t *core, qdr_connection_t *conn) // qdr_auto_link_t *al = DEQ_HEAD(cid->auto_link_refs); while (al) { - //qdr_link_route_deactivate_CT(core, lr, conn); + qdr_auto_link_deactivate_CT(core, al, conn); al = DEQ_NEXT_N(REF, al); } http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/c8f2c9df/src/router_core/route_control.h ---------------------------------------------------------------------- diff --git a/src/router_core/route_control.h b/src/router_core/route_control.h index 7738c6d..34b8bd9 100644 --- a/src/router_core/route_control.h +++ b/src/router_core/route_control.h @@ -31,13 +31,13 @@ void qdr_route_add_link_route_CT(qdr_core_t *core, void qdr_route_del_link_route_CT(qdr_core_t *core, qdr_link_route_t *lr); -void qdr_route_add_auto_link_CT(qdr_core_t *core, - qd_field_iterator_t *name, - qd_parsed_field_t *addr_field, - qd_direction_t dir, - int phase, - qd_parsed_field_t *conn_id, - bool is_container); +qdr_auto_link_t *qdr_route_add_auto_link_CT(qdr_core_t *core, + qd_field_iterator_t *name, + qd_parsed_field_t *addr_field, + qd_direction_t dir, + int phase, + qd_parsed_field_t *conn_id, + bool is_container); void qdr_route_del_auto_link_CT(qdr_core_t *core, qdr_auto_link_t *auto_link); http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/c8f2c9df/src/router_core/router_core_private.h ---------------------------------------------------------------------- diff --git a/src/router_core/router_core_private.h b/src/router_core/router_core_private.h index 28e4aef..6956b67 100644 --- a/src/router_core/router_core_private.h +++ b/src/router_core/router_core_private.h @@ -228,6 +228,7 @@ struct qdr_link_t { qdr_address_t *owning_addr; ///< [ref] Address record that owns this link qdr_link_t *connected_link; ///< [ref] If this is a link-route, reference the connected link qdr_link_ref_t *ref[QDR_LINK_LIST_CLASSES]; ///< Pointers to containing reference objects + qdr_auto_link_t *auto_link; ///< [ref] Auto_link that owns this link qdr_delivery_list_t undelivered; ///< Deliveries to be forwarded or sent qdr_delivery_list_t unsettled; ///< Unsettled deliveries qdr_delivery_ref_list_t updated_deliveries; ///< References to deliveries (in the unsettled list) with updates. @@ -417,6 +418,15 @@ ALLOC_DECLARE(qdr_link_route_t); DEQ_DECLARE(qdr_link_route_t, qdr_link_route_list_t); +typedef enum { + QDR_AUTO_LINK_STATE_INACTIVE, + QDR_AUTO_LINK_STATE_ATTACHING, + QDR_AUTO_LINK_STATE_FAILED, + QDR_AUTO_LINK_STATE_ACTIVE, + QDR_AUTO_LINK_STATE_QUIESCING, + QDR_AUTO_LINK_STATE_IDLE +} qdr_auto_link_state_t; + struct qdr_auto_link_t { DEQ_LINKS(qdr_auto_link_t); DEQ_LINKS_N(REF, qdr_auto_link_t); @@ -427,6 +437,8 @@ struct qdr_auto_link_t { qd_direction_t dir; qdr_conn_identifier_t *conn_id; qdr_link_t *link; + qdr_auto_link_state_t state; + char *last_error; }; ALLOC_DECLARE(qdr_auto_link_t); @@ -550,11 +562,19 @@ void qdr_check_addr_CT(qdr_core_t *core, qdr_address_t *addr, bool was_local); qdr_delivery_t *qdr_forward_new_delivery_CT(qdr_core_t *core, qdr_delivery_t *peer, qdr_link_t *link, qd_message_t *msg); void qdr_forward_deliver_CT(qdr_core_t *core, qdr_link_t *link, qdr_delivery_t *dlv); void qdr_connection_activate_CT(qdr_core_t *core, qdr_connection_t *conn); +qd_address_treatment_t qdr_treatment_for_address_CT(qdr_core_t *core, qd_field_iterator_t *iter); void qdr_connection_enqueue_work_CT(qdr_core_t *core, qdr_connection_t *conn, qdr_connection_work_t *work); +qdr_link_t *qdr_create_link_CT(qdr_core_t *core, + qdr_connection_t *conn, + qd_link_type_t link_type, + qd_direction_t dir, + qdr_terminus_t *source, + qdr_terminus_t *target); + qdr_query_t *qdr_query(qdr_core_t *core, void *context, qd_router_entity_type_t type, http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/c8f2c9df/src/router_node.c ---------------------------------------------------------------------- diff --git a/src/router_node.c b/src/router_node.c index 5d70cb9..3a9b8af 100644 --- a/src/router_node.c +++ b/src/router_node.c @@ -65,6 +65,9 @@ static void qd_router_connection_get_config(const qd_connection_t *conn, *role = QDR_ROLE_NORMAL; *name = cf->name; + if (strncmp("listener/", *name, 9) == 0 || + strncmp("connector/", *name, 10) == 0) + *name = 0; } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
