Return to reg_table.cpp CVS log | Up to [Pegasus] / pegasus / src / Pegasus / Server |
File: [Pegasus] / pegasus / src / Pegasus / Server / reg_table.cpp
(download)
Revision: 1.7, Sat May 3 09:55:17 2003 UTC (21 years, 2 months ago) by mday Branch: MAIN CVS Tags: test, pep_88, TEST, RELEASE_2_3_0-msg-freeze, RELEASE_2_2_1-snapshot, RELEASE_2_2_0_0-release, RELEASE_2_2_0-root, RELEASE_2_2_0-branch, RELEASE_2_2-root, PRE_LICENSE_UPDATE_2003, PEGASUS_FC_VERSION_2_2 Changes since 1.6: +70 -3 lines bugzilla 295 |
//%///////////-*-c++-*-///////////////////////////////////////////////////////// // // Copyright (c) 2000, 2001, 2002 BMC Software, Hewlett-Packard Company, IBM, // The Open Group, Tivoli Systems // // Copyright (c) 2003 Hewlett-Packard Company, IBM,The Open Group // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // THE ABOVE COPYRIGHT NOTICE AND THIS PERMISSION NOTICE SHALL BE INCLUDED IN // ALL COPIES OR SUBSTANTIAL PORTIONS OF THE SOFTWARE. THE SOFTWARE IS PROVIDED // "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // //============================================================================== // // Author: Mike Day (mdday@us.ibm.com) // // Modified By: // //%//////////////////////////////////////////////////////////////////////////// #include <Pegasus/Common/MessageQueueService.h> #include <Pegasus/Common/HashTable.h> #include <Pegasus/Common/IPC.h> #include <Pegasus/Common/Sharable.h> #include <Pegasus/Common/CIMName.h> #include "reg_table.h" PEGASUS_USING_STD; PEGASUS_NAMESPACE_BEGIN const DynamicRoutingTable _internal_routing_table; class reg_table_record { private: reg_table_record(void); virtual ~reg_table_record(void); reg_table_record(const CIMName & class_name, const CIMNamespaceName & namespace_name, Uint32 type, Uint32 flags, const MessageQueueService * destination_service); reg_table_record(const CIMName & class_name, const CIMNamespaceName & namespace_name, Uint32 type, const Array<Uint8> & extended_type, Uint32 flags, const Array<Uint8> & extended_flags, const MessageQueueService * destination_service); reg_table_record(const CIMName & class_name, const CIMNamespaceName & namespace_name, Uint32 type, Uint32 flags, const MessageQueueService * destination_service, const String & provider_name, const String & module_name); reg_table_record(const CIMName & class_name, const CIMNamespaceName & namespace_name, Uint32 type, const Array<Uint8> & extended_type, Uint32 flags, const Array<Uint8> & extended_flags, const MessageQueueService * destination_service, const String & provider_name, const String & module_name); reg_table_record(const reg_table_record & ); reg_table_record & operator =(const reg_table_record & ); void dump(void); CIMName class_name; CIMNamespaceName namespace_name; Uint32 type; Array<Uint8> extended_key; Uint32 flags; Array<Uint8> extended_flags; MessageQueueService *service; String provider_name; String module_name; friend class reg_table_rep; friend class DynamicRoutingTable; }; reg_table_record::reg_table_record(void) : class_name(String::EMPTY), namespace_name(String::EMPTY), type(0xffffffff), extended_key(), flags(0xffffffff), extended_flags(), service(0) { } reg_table_record::reg_table_record( const CIMName &_name, const CIMNamespaceName &_ns, Uint32 _type, Uint32 _flags, const MessageQueueService * _svce) : class_name(_name), namespace_name(_ns), type(_type), extended_key(), flags(_flags), extended_flags(), service(const_cast<MessageQueueService *>(_svce)) { } reg_table_record::reg_table_record( const CIMName &_name, const CIMNamespaceName &_ns, Uint32 _type, const Array<Uint8> & _type_key, Uint32 _flags, const Array<Uint8> & _ext_flags, const MessageQueueService * _svce) : class_name(_name), namespace_name(_ns), type(_type), extended_key(_type_key), flags(_flags), extended_flags(_ext_flags), service(const_cast<MessageQueueService *>(_svce)) { } reg_table_record::reg_table_record( const CIMName &_name, const CIMNamespaceName &_ns, Uint32 _type, Uint32 _flags, const MessageQueueService * _svce, const String & _provider_name, const String & _module_name) : class_name(_name), namespace_name(_ns), type(_type), extended_key(), flags(_flags), extended_flags(), service(const_cast<MessageQueueService *>(_svce)), provider_name(_provider_name), module_name(_module_name) { } reg_table_record::reg_table_record( const CIMName &_name, const CIMNamespaceName &_ns, Uint32 _type, const Array<Uint8> & _type_key, Uint32 _flags, const Array<Uint8> & _ext_flags, const MessageQueueService * _svce, const String & _provider_name, const String & _module_name) : class_name(_name), namespace_name(_ns), type(_type), extended_key(_type_key), flags(_flags), extended_flags(_ext_flags), service(const_cast<MessageQueueService *>(_svce)), provider_name(_provider_name), module_name(_module_name) { } reg_table_record::reg_table_record(const reg_table_record & rtr) { class_name = rtr.class_name; namespace_name = rtr.namespace_name; type = rtr.type; extended_key = rtr.extended_key; flags = rtr.flags; extended_flags = rtr.extended_flags; service = rtr.service; provider_name = rtr.provider_name; module_name = rtr.module_name; } reg_table_record:: ~reg_table_record(void) { } reg_table_record & reg_table_record::operator =(const reg_table_record & other) { if(this != &other) { class_name = other.class_name; namespace_name = other.namespace_name; type = other.type; extended_key = other.extended_key; flags = other.flags; extended_flags = other.extended_flags; service = other.service; provider_name = other.provider_name; module_name = other.module_name; } return *this; } void reg_table_record::dump(void) { PEGASUS_STD(cout) << "----Dumping Reg Table Record:----" << PEGASUS_STD(endl); PEGASUS_STD(cout) << class_name.getString() << PEGASUS_STD(endl); PEGASUS_STD(cout) << namespace_name.getString() << PEGASUS_STD(endl); PEGASUS_STD(cout) << type << PEGASUS_STD(endl); PEGASUS_STD(cout) << "---------------------------------" << PEGASUS_STD(endl); } // instead of concatenating class, namespace, type into a single key, use a 3-level set-associative cache // implemented with hash tables for namespace, class, and type. Because key composition is not // necessary it should be faster than a fully associative cache. struct RegTableEqual { static Boolean equal(const String & x, const String & y) { if (0 == String::compareNoCase(x, y)) return true; return false; } }; struct RegTableHash { static Uint32 hash(const String & str) { String cpy(str); cpy.toLower(); Uint32 h = 0; for(Uint32 i = 0, n = cpy.size(); i < n; i++) h = 5 * h + cpy[i]; return h; } }; typedef HashTable<String, struct reg_table_record *, RegTableEqual, RegTableHash > routing_table; typedef HashTable<Uint32, routing_table *, EqualFunc<Uint32>, HashFunc<Uint32> > type_table; typedef HashTable<String, type_table *, RegTableEqual, RegTableHash > namespace_table; class reg_table_rep : public Sharable { public: reg_table_rep(void) { } ~reg_table_rep(void) { } private: // store a record Boolean _insert(const reg_table_record & rec); // retrieve a pointer to the stored record const reg_table_record * find(const reg_table_record& rec); const reg_table_record * find_extended(const reg_table_record& rec); void find(const reg_table_record& rec, Array<reg_table_record *> *results); void find_extended(const reg_table_record& rec, Array<reg_table_record *> *results); // remove the record and retrieve a pointer to it reg_table_record * release(const reg_table_record& rec); reg_table_record *release_extended(const reg_table_record& rec); void release(const reg_table_record& rec, Array<reg_table_record *> *results); void release_extended(const reg_table_record& rec, Array<reg_table_record *> *results); // remove and destroy a record or records void destroy(const reg_table_record& rec); void destroy_extended(const reg_table_record& rec); void destroy_all(void); Uint32 remove_by_router(const reg_table_record& rec); static const Uint32 FIND; static const Uint32 REMOVE; static const Uint32 MULTIPLE; static const Uint32 DESTROY; static const Uint32 EXTENDED; static const Uint32 SERVICE; static const Uint32 STRINGS; reg_table_record * _find(const reg_table_record & rec, Uint32 flags, Array<reg_table_record *> *arr_ptr = 0); // "wildcard" routines that use iterators // ns.isNull() == true means enumerate for all name spaces // cls.isNull() == true means enumerate for all classes // type == 0xffffffff means enumerate for all types void _enumerate(const reg_table_record & rec, Uint32 flags, Array<reg_table_record *> *arr_ptr = 0); Boolean _extended_match(const reg_table_record & rec_a, const reg_table_record & rec_b) const; void _dump_table(); namespace_table _table; Mutex _mutex; friend class DynamicRoutingTable; }; const Uint32 reg_table_rep::FIND = 0x00000001; const Uint32 reg_table_rep::REMOVE = 0x00000002; const Uint32 reg_table_rep::MULTIPLE = 0x00000004; const Uint32 reg_table_rep::DESTROY = 0x00000008; const Uint32 reg_table_rep::EXTENDED = 0x00000010; const Uint32 reg_table_rep::SERVICE = 0x00000020; const Uint32 reg_table_rep::STRINGS = 0x00000020; // insert optimized for simplicity, not speed Boolean reg_table_rep::_insert(const reg_table_record &rec) { // ipc synchronization auto_mutex monitor(&_mutex); type_table *tt = 0; if(false == _table.lookup(rec.namespace_name.getString(), tt)) { type_table *temp = new type_table(); _table.insert(rec.namespace_name.getString(), temp); if(false == _table.lookup(rec.namespace_name.getString(), tt)) return false; } routing_table *rt = 0; if(false == tt->lookup(rec.type, rt)) { routing_table *temp = new routing_table(); tt->insert(rec.type, temp); if(false == tt->lookup(rec.type, rt)) return false; } Logger::put(Logger::STANDARD_LOG, System::CIMSERVER, Logger::TRACE, "reg_table_rep::_insert - Inserting provider $0 into the provider reqistration table,", rec.class_name.getString()); return rt->insert(rec.class_name.getString(), new reg_table_record(rec)); } const reg_table_record * reg_table_rep::find(const reg_table_record & rec) { return _find(rec, FIND); } const reg_table_record * reg_table_rep::find_extended(const reg_table_record & rec) { return _find(rec, FIND | EXTENDED); } void reg_table_rep::find(const reg_table_record & rec, Array<reg_table_record *> *results) { _find(rec, (FIND | MULTIPLE), results); } void reg_table_rep::find_extended(const reg_table_record & rec, Array<reg_table_record *> *results) { _find(rec, (FIND | MULTIPLE | EXTENDED), results); } reg_table_record * reg_table_rep::release(const reg_table_record& rec) { return _find(rec, (FIND | REMOVE)); } reg_table_record * reg_table_rep::release_extended(const reg_table_record &rec) { return _find(rec, (FIND | REMOVE | EXTENDED) ); } void reg_table_rep::release(const reg_table_record& rec, Array<reg_table_record *> *results) { _find(rec, (FIND | REMOVE), results); } void reg_table_rep::release_extended(const reg_table_record &rec, Array<reg_table_record *> *results) { _find(rec, (FIND | REMOVE | EXTENDED), results); } void reg_table_rep::destroy(const reg_table_record & rec) { _find(rec, (FIND | REMOVE | DESTROY)); } void reg_table_rep::destroy_extended(const reg_table_record& rec) { _find(rec, (FIND | REMOVE | DESTROY | EXTENDED)); } void reg_table_rep::destroy_all(void) { reg_table_record rec; _find(rec, (FIND | REMOVE | DESTROY | MULTIPLE )); } Uint32 reg_table_rep::remove_by_router(const reg_table_record& rec) { Uint32 ret; Array<reg_table_record *> results; _find(rec, (FIND | REMOVE | DESTROY | MULTIPLE | SERVICE), &results); ret = results.size(); for(Uint32 i = 0; i < results.size() ; ++i) { delete results[i]; results[i] = 0; } return ret; } reg_table_record * reg_table_rep::_find(const reg_table_record &rec, Uint32 flags, Array<reg_table_record *> *arr_ptr) { // ipc synchronization auto_mutex monitor(&_mutex); type_table *tt = 0; Boolean try_again = true; if(flags & MULTIPLE ) { _enumerate(rec, flags, arr_ptr); return 0; } else { // find the type entry if(true == _table.lookup(rec.namespace_name.getString(), tt)) { try_again_wild_namespace: routing_table *rt; if(true == tt->lookup(rec.type, rt)) { reg_table_record *record ; while(true == rt->lookup(rec.class_name.getString(), record)) { // flags value of all foxes always matches if(rec.flags != 0xffffffff) if(record->flags != rec.flags) continue; if(flags & EXTENDED) { if(false == _extended_match(rec, *record)) continue; } if(flags & REMOVE || flags & DESTROY) { rt->remove(rec.class_name.getString()); if(flags & DESTROY) { delete record; record = 0; } } return record; } } } } // now try again using the special wildcard namespace table CIMNamespaceName _wild; _wild.clear(); if(true == try_again && true == _table.lookup(_wild.getString(), tt)) { try_again = false; goto try_again_wild_namespace; } return 0; } // do not call directly - does not lock the reg table mutex !! void reg_table_rep::_enumerate(const reg_table_record & rec, Uint32 flags, Array<reg_table_record *> *results) { _enuerate_start: if(flags & MULTIPLE) { if(results == 0 && ! (flags & DESTROY)) return; for(namespace_table::Iterator i = _table.start(); i; i++) { // null namespace_name is a wildcard if(false == rec.namespace_name.isNull()) { if(rec.namespace_name.getString() != i.key()) continue; } for(type_table::Iterator y = i.value()->start(); y != 0 ; y++) { // type of -1 is a wildcard if(rec.type != 0xffffffff) { if(rec.type != y.key()) continue; } for(routing_table::Iterator x = y.value()->start(); x != 0; x++) { // null class_name is a wildcard if(false == rec.class_name.isNull()) { if(!rec.class_name.equal(x.value()->class_name)) continue; } reg_table_record *tmp = x.value(); // flags value of all foxes always matches if(rec.flags != 0xffffffff) if(tmp->flags != rec.flags) continue; if(flags & EXTENDED) { if(false == _extended_match(rec, *tmp)) continue; } if(flags & SERVICE) { if(rec.service != tmp->service) continue; } if(flags & REMOVE || flags & DESTROY) { y.value()->remove(x.key()); if(flags & DESTROY) { delete tmp; } goto _enuerate_start; } else { results->append(tmp); } } } } } } Boolean reg_table_rep::_extended_match(const reg_table_record & rec_a, const reg_table_record & rec_b) const { Uint32 rec_size; if((rec_size = rec_a.extended_key.size()) != rec_b.extended_key.size()) return false; Uint32 i = 0; do { if(! (rec_a.extended_key[i] == rec_b.extended_key[i]) ) return false; ++i; }while (i < rec_size ); return true; } void reg_table_rep::_dump_table() { PEGASUS_STD(cout) <<"******** Dumping Reg Table ********" << PEGASUS_STD(endl); auto_mutex monitor(&_mutex); for(namespace_table::Iterator i = _table.start(); i; i++) { PEGASUS_STD(cout) << "Namespace: " << i.key() << PEGASUS_STD(endl); for(type_table::Iterator y = i.value()->start(); y != 0 ; y++) { PEGASUS_STD(cout) << "Type: " << y.key() << PEGASUS_STD(endl); for(routing_table::Iterator x = y.value()->start(); x != 0; x++) { reg_table_record *tmp = x.value(); tmp->dump(); } } } } const Uint32 DynamicRoutingTable:: INTERNAL = 0x00000001; const Uint32 DynamicRoutingTable:: INSTANCE = 0x00000002; const Uint32 DynamicRoutingTable:: CLASS = 0x00000003; const Uint32 DynamicRoutingTable:: METHOD = 0x00000004; const Uint32 DynamicRoutingTable:: ASSOCIATION = 0x00000005; const Uint32 DynamicRoutingTable:: QUERY = 0x00000006; const Uint32 DynamicRoutingTable:: INDICATION = 0x00000007; const Uint32 DynamicRoutingTable:: CONSUMER = 0x00000008; const Uint32 DynamicRoutingTable:: SERVICE = 0x00000009; const Uint32 DynamicRoutingTable:: AUTHORIZATION = 0x0000000a; const Uint32 DynamicRoutingTable:: AUTHENTICATION = 0x0000000b; const Uint32 DynamicRoutingTable:: ENCODE_HOOK = 0x0000000c; const Uint32 DynamicRoutingTable:: DECODE_HOOK = 0x0000000d; const Uint32 DynamicRoutingTable:: EXTENDED = 0x0000000e; DynamicRoutingTable::DynamicRoutingTable(void) { _rep = new reg_table_rep(); } DynamicRoutingTable::~DynamicRoutingTable(void) { Dec(_rep); } DynamicRoutingTable::DynamicRoutingTable(const DynamicRoutingTable & table) { if(this != &table) { Inc(_rep = table._rep); } } DynamicRoutingTable & DynamicRoutingTable::operator =(const DynamicRoutingTable & table) { if(this != &table) { Inc(_rep = table._rep); } return *this; } const DynamicRoutingTable DynamicRoutingTable::get_ro_routing_table(void) { return DynamicRoutingTable(_internal_routing_table); } DynamicRoutingTable DynamicRoutingTable::get_rw_routing_table(void) { return DynamicRoutingTable(_internal_routing_table); } // get a single service that can route this spec. MessageQueueService * DynamicRoutingTable::get_routing(const CIMName & classname, const CIMNamespaceName & ns, Uint32 type, Uint32 flags) const { reg_table_record rec(classname, ns, type, flags, 0); const reg_table_record *ret = _rep->find(rec); if(ret) return ret->service; return 0; } MessageQueueService * DynamicRoutingTable::get_routing(const CIMName & classname, const CIMNamespaceName & ns, Uint32 type, Uint32 flags, String & provider, String & module) const { reg_table_record rec(classname, ns, type, flags, 0, provider, module); const reg_table_record *ret = _rep->find(rec); if(ret) { provider = ret->provider_name; module = ret->module_name; return ret->service; } return 0; } // get a single service that can route this extended spec. MessageQueueService * DynamicRoutingTable::get_routing(const CIMName& classname, const CIMNamespaceName& ns, Uint32 type, const Array<Uint8>& extended_type, Uint32 flags, const Array<Uint8>& extended_flags) const { reg_table_record rec(classname, ns, type, extended_type, flags, extended_flags, 0); const reg_table_record *ret = _rep->find_extended(rec); if(ret) return ret->service; return 0; } // get a single service that can route this extended spec. MessageQueueService * DynamicRoutingTable::get_routing(const CIMName& classname, const CIMNamespaceName& ns, Uint32 type, const Array<Uint8>& extended_type, Uint32 flags, const Array<Uint8>& extended_flags, String & provider, String & module) const { reg_table_record rec(classname, ns, type, extended_type, flags, extended_flags, 0, provider, module); const reg_table_record *ret = _rep->find_extended(rec); if(ret) { provider = ret->provider_name; module = ret->module_name; return ret->service; } return 0; } // get an array of services that can route this spec void DynamicRoutingTable::get_routing(const CIMName& classname, const CIMNamespaceName& ns, Uint32 type, Uint32 flags, Array<MessageQueueService*>& results) const { reg_table_record rec(classname, ns, type, flags, 0); Array<reg_table_record*> records; _rep->find(rec, &records); for(Uint32 i = 0; i < records.size(); ++i ) { // do not include null service pointers in results if(records[i]->service) results.append((records[i])->service); } } // get an array of services that can route this extended spec. void DynamicRoutingTable::get_routing(const CIMName& classname, const CIMNamespaceName& ns, Uint32 type, const Array<Uint8>& extended_type, Uint32 flags, const Array<Uint8>& extended_flags, Array<MessageQueueService*>& results) const { reg_table_record rec(classname, ns, type, extended_type, flags, extended_flags, 0); Array<reg_table_record*> records; _rep->find_extended(rec, &records); for(Uint32 i = 0; i < records.size(); ++i ) { // do not include null service pointers in results if(records[i]->service) results.append((records[i])->service); } } Boolean DynamicRoutingTable::insert_record(const CIMName& classname, const CIMNamespaceName& ns, Uint32 type, Uint32 flags, MessageQueueService* svce) { reg_table_record rec(classname, ns, type, flags, svce); return (_rep->_insert(rec)); } Boolean DynamicRoutingTable::insert_record(const CIMName& classname, const CIMNamespaceName& ns, Uint32 type, Uint32 flags, MessageQueueService* svce, const String& provider, const String& module) { reg_table_record rec(classname, ns, type, flags, svce, provider, module); return (_rep->_insert(rec)); } Boolean DynamicRoutingTable::insert_record(const CIMName& classname, const CIMNamespaceName& ns, Uint32 type, const Array<Uint8>& extended_type, Uint32 flags, const Array<Uint8>& extended_flags, MessageQueueService* svce) { reg_table_record rec(classname, ns, type, extended_type, flags, extended_flags, svce); return (_rep->_insert(rec)); } Boolean DynamicRoutingTable::insert_record(const CIMName& classname, const CIMNamespaceName& ns, Uint32 type, const Array<Uint8>& extended_type, Uint32 flags, const Array<Uint8>& extended_flags, MessageQueueService* svce, const String& provider, const String& module) { reg_table_record rec(classname, ns, type, extended_type, flags, extended_flags, svce, provider, module); return (_rep->_insert(rec)); } MessageQueueService * DynamicRoutingTable::remove_record(const CIMName& classname, const CIMNamespaceName& ns, Uint32 type, Uint32 flags) { MessageQueueService *svce = 0; reg_table_record rec(classname, ns, type, flags, 0); const reg_table_record *ret = _rep->release(rec); if(ret) svce = ret->service; delete ret; return svce; } MessageQueueService * DynamicRoutingTable::remove_record(const CIMName& classname, const CIMNamespaceName& ns, Uint32 type, const Array<Uint8>& extended_type, Uint32 flags, const Array<Uint8>& extended_flags) { MessageQueueService *svce = 0; reg_table_record rec(classname, ns, type, extended_type, flags, extended_flags, 0); const reg_table_record *ret = _rep->release_extended(rec); if(ret) svce = rec.service; delete ret; return svce; } Uint32 DynamicRoutingTable::remove_multiple_records(const CIMName& classname, const CIMNamespaceName& ns, Uint32 type, Uint32 flags) { Uint32 ret = 0; Array<reg_table_record *> results; reg_table_record rec(classname, ns, type, flags, 0); _rep->release(rec, &results); ret = results.size(); for(Uint32 i = 0; i < results.size(); ++i) { delete results[i]; results[i] = 0; } return ret; } Uint32 DynamicRoutingTable::remove_multiple_records(const CIMName& classname, const CIMNamespaceName& ns, Uint32 type, const Array<Uint8>& extended_type, Uint32 flags, const Array<Uint8>& extended_flags) { Uint32 ret = 0; Array<reg_table_record *> results; reg_table_record rec(classname, ns, type, extended_type, flags, extended_flags, 0); _rep->release_extended(rec, &results); ret = results.size(); for(Uint32 i = 0; i < results.size(); ++i) { delete results[i]; results[i] = 0; } return ret; } Uint32 DynamicRoutingTable::remove_router_records(const MessageQueueService* router) { CIMName classname; classname.clear(); CIMNamespaceName ns; ns.clear(); reg_table_record rec(classname, ns, 0xffffffff, 0xffffffff, router); return _rep->remove_by_router(rec); } void DynamicRoutingTable::dump_table(void) { _rep->_dump_table(); } PEGASUS_NAMESPACE_END
No CVS admin address has been configured |
Powered by ViewCVS 0.9.2 |