(file) Return to CIMListener.cpp CVS log (file) (dir) Up to [Pegasus] / pegasus / src / Pegasus / Listener

  1 karl  1.28 //%2005////////////////////////////////////////////////////////////////////////
  2 kumpf 1.1  //
  3 karl  1.25 // Copyright (c) 2000, 2001, 2002 BMC Software; Hewlett-Packard Development
  4            // Company, L.P.; IBM Corp.; The Open Group; Tivoli Systems.
  5            // Copyright (c) 2003 BMC Software; Hewlett-Packard Development Company, L.P.;
  6 karl  1.12 // IBM Corp.; EMC Corporation, The Open Group.
  7 karl  1.25 // Copyright (c) 2004 BMC Software; Hewlett-Packard Development Company, L.P.;
  8            // IBM Corp.; EMC Corporation; VERITAS Software Corporation; The Open Group.
  9 karl  1.28 // Copyright (c) 2005 Hewlett-Packard Development Company, L.P.; IBM Corp.;
 10            // EMC Corporation; VERITAS Software Corporation; The Open Group.
 11 kumpf 1.1  //
 12            // Permission is hereby granted, free of charge, to any person obtaining a copy
 13            // of this software and associated documentation files (the "Software"), to
 14            // deal in the Software without restriction, including without limitation the
 15            // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 16            // sell copies of the Software, and to permit persons to whom the Software is
 17            // furnished to do so, subject to the following conditions:
 18 david.dillard 1.31 //
 19 kumpf         1.1  // THE ABOVE COPYRIGHT NOTICE AND THIS PERMISSION NOTICE SHALL BE INCLUDED IN
 20                    // ALL COPIES OR SUBSTANTIAL PORTIONS OF THE SOFTWARE. THE SOFTWARE IS PROVIDED
 21                    // "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
 22                    // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 23                    // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 24                    // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 25                    // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 26                    // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 27                    //
 28                    //==============================================================================
 29                    //
 30 tony          1.8  // Author: Dong Xiang, EMC Corporation (xiang_dong@emc.com)
 31 kumpf         1.1  //
 32 dj.gorey      1.14 // Modified By:   Dan Gorey (djgorey@us.ibm.com)
 33 a.arora       1.21 //                Amit K Arora, IBM (amita@in.ibm.com) for PEP#183
 34 kumpf         1.26 //                Roger Kumpf, Hewlett-Packard Company (roger_kumpf@hp.com)
 35 david.dillard 1.31 //                David Dillard, VERITAS Software Corp.
 36                    //                    (david.dillard@veritas.com)
 37 kumpf         1.1  //
 38                    //%/////////////////////////////////////////////////////////////////////////////
 39                    
 40 tony          1.8  #include "CIMListener.h"
 41 kumpf         1.1  
 42 tony          1.8  #include <Pegasus/Common/Exception.h>
 43                    #include <Pegasus/Common/SSLContext.h>
 44                    #include <Pegasus/Common/Monitor.h>
 45 kumpf         1.1  #include <Pegasus/Common/HTTPAcceptor.h>
 46 kumpf         1.11 #include <Pegasus/Common/PegasusVersion.h>
 47 kumpf         1.2  
 48 kumpf         1.1  #include <Pegasus/ExportServer/CIMExportResponseEncoder.h>
 49                    #include <Pegasus/ExportServer/CIMExportRequestDecoder.h>
 50                    
 51 tony          1.8  #include <Pegasus/Consumer/CIMIndicationConsumer.h>
 52                    #include <Pegasus/Listener/CIMListenerIndicationDispatcher.h>
 53                    
 54                    PEGASUS_NAMESPACE_BEGIN
 55                    /////////////////////////////////////////////////////////////////////////////
 56                    // CIMListenerService
 57                    /////////////////////////////////////////////////////////////////////////////
 58                    class CIMListenerService
 59                    {
 60                    public:
 61                    	CIMListenerService(Uint32 portNumber, SSLContext* sslContext=NULL);
 62                    	CIMListenerService(CIMListenerService& svc);
 63                      ~CIMListenerService();
 64                    
 65                    	void				init();
 66                    	/** bind to the port
 67                    	*/
 68                    	void				bind();
 69                    	/** runForever Main runloop for the server.
 70                    	*/
 71                    	void runForever();
 72 david.dillard 1.31 
 73 tony          1.8  	/** Call to gracefully shutdown the server.  The server connection socket
 74                    	will be closed to disable new connections from clients.
 75                    	*/
 76                    	void stopClientConnection();
 77 david.dillard 1.31 
 78 tony          1.8  	/** Call to gracefully shutdown the server.  It is called when the server
 79                    	has been stopped and is ready to be shutdown.  Next time runForever()
 80                    	is called, the server shuts down.
 81                    	*/
 82                    	void shutdown();
 83 david.dillard 1.31 
 84 tony          1.8  	/** Return true if the server has shutdown, false otherwise.
 85                    	*/
 86                    	Boolean terminated() { return _dieNow; };
 87 david.dillard 1.31 
 88 tony          1.8  	/** Call to resume the sever.
 89                    	*/
 90                    	void resume();
 91 david.dillard 1.31 
 92 tony          1.8  	/** Call to set the CIMServer state.  Also inform the appropriate
 93                    	message queues about the current state of the CIMServer.
 94                    	*/
 95                    	void setState(Uint32 state);
 96 david.dillard 1.31 
 97 tony          1.8  	Uint32 getOutstandingRequestCount();
 98                    
 99                    	/** Returns the indication listener dispatcher
100                    	 */
101                    	CIMListenerIndicationDispatcher* getIndicationDispatcher() const;
102                    
103                      /** Returns the indication listener dispatcher
104                    	 */
105                    	void setIndicationDispatcher(CIMListenerIndicationDispatcher* dispatcher);
106                    
107                    	static PEGASUS_THREAD_RETURN PEGASUS_THREAD_CDECL _listener_routine(void *param);
108                    
109                    private:
110                    	Uint32			_portNumber;
111                    	SSLContext* _sslContext;
112                    	Monitor*				_monitor;
113                      HTTPAcceptor*   _acceptor;
114 dj.gorey      1.14 
115                      Boolean					_dieNow;
116 tony          1.8  
117                      CIMListenerIndicationDispatcher* _dispatcher;
118                    
119                      CIMExportResponseEncoder* _responseEncoder;
120                      CIMExportRequestDecoder*  _requestDecoder;
121                    
122                    };
123                    
124                    CIMListenerService::CIMListenerService(Uint32 portNumber, SSLContext* sslContext)
125                    :_portNumber(portNumber)
126                    ,_sslContext(sslContext)
127                    ,_monitor(NULL)
128                    ,_acceptor(NULL)
129                    ,_dieNow(false)
130                    ,_dispatcher(NULL)
131                    ,_responseEncoder(NULL)
132                    ,_requestDecoder(NULL)
133                    {
134                    }
135                    
136                    CIMListenerService::CIMListenerService(CIMListenerService& svc)
137 tony          1.8  :_portNumber(svc._portNumber)
138                    ,_sslContext(svc._sslContext)
139                    ,_monitor(NULL)
140                    ,_acceptor(NULL)
141                    ,_dieNow(svc._dieNow)
142                    ,_dispatcher(NULL)
143                    ,_responseEncoder(NULL)
144                    ,_requestDecoder(NULL)
145                    {
146                    }
147                    CIMListenerService::~CIMListenerService()
148                    {
149                    	// if port is alive, clean up the port
150                    	//if(_sslContext!=NULL)
151                    	//	delete _sslContext;
152                    
153                    	if(_responseEncoder!=NULL)
154                    		delete _responseEncoder;
155                    
156                    	if(_requestDecoder!=NULL)
157                    		delete _requestDecoder;
158 kumpf         1.1  
159 tony          1.8  	//if(_dispatcher!=NULL)
160                    	//	delete _dispatcher;
161 kumpf         1.1  
162 tony          1.8  	if(_monitor!=NULL)
163                    		delete _monitor;
164 kumpf         1.1  
165 tony          1.8  	if(_acceptor!=NULL)
166                    		delete _acceptor;
167                    }
168 kumpf         1.1  
169 tony          1.8  void CIMListenerService::init()
170 kumpf         1.1  {
171 tony          1.8  	PEG_METHOD_ENTER(TRC_LISTENER, "CIMListenerService::init");
172 kumpf         1.1  
173 kumpf         1.27   _monitor = new Monitor();
174 david.dillard 1.31 
175 tony          1.8  	//_dispatcher = new CIMListenerIndicationDispatcher();
176 kumpf         1.1  
177 chuck         1.20   _responseEncoder = new CIMExportResponseEncoder();
178 tony          1.8    _requestDecoder = new CIMExportRequestDecoder(
179                    		_dispatcher,
180                    		_responseEncoder->getQueueId());
181                    
182 dj.gorey      1.14   _acceptor = new HTTPAcceptor(
183 david.dillard 1.31 		 _monitor,
184                    		 _requestDecoder,
185                    		 false,
186                    		 _portNumber,
187 kumpf         1.19 		 _sslContext,
188                                     false);
189 kumpf         1.1  
190 chuck         1.20   bind();
191 kumpf         1.1  
192 chuck         1.20   PEG_METHOD_EXIT();
193 tony          1.8  }
194                    void CIMListenerService::bind()
195                    {
196 chuck         1.20   if(_acceptor!=NULL)
197                        { // Bind to the port
198                          _acceptor->bind();
199 tony          1.8  
200 chuck         1.20       PEGASUS_STD(cout) << "Listening on HTTP port " << _portNumber << PEGASUS_STD(endl);
201 david.dillard 1.31 
202 chuck         1.20       //listener.addAcceptor(false, portNumberHttp, false);
203                          Logger::put(Logger::STANDARD_LOG, System::CIMLISTENER, Logger::INFORMATION,
204 tony          1.8                          "Listening on HTTP port $0.", _portNumber);
205 kumpf         1.1  
206 chuck         1.20     }
207 tony          1.8  }
208 chuck         1.20 
209 tony          1.8  void CIMListenerService::runForever()
210                    {
211 chuck         1.20   static int modulator = 0;
212 kumpf         1.1  
213 chuck         1.20   if(!_dieNow)
214 dj.gorey      1.14     {
215 david.dillard 1.31       if(false == _monitor->run(500000))
216                    	{
217 chuck         1.20 	  modulator++;
218 david.dillard 1.31       try
219 a.arora       1.21       {
220                    	     //MessageQueueService::_check_idle_flag = 1;
221                    		 //MessageQueueService::_polling_sem.signal();
222 kumpf         1.30 		 MessageQueueService::get_thread_pool()->cleanupIdleThreads();
223 a.arora       1.21       }
224                    	  catch(...)
225                          {
226                          }
227 chuck         1.20 	}
228                    /*
229                          if (handleShutdownSignal)
230                          {
231                            Tracer::trace(TRC_SERVER, Tracer::LEVEL3,
232                    	"CIMServer::runForever - signal received.  Shutting down.");
233                    
234                    	ShutdownService::getInstance(this)->shutdown(true, 10, false);
235                    	handleShutdownSignal = false;
236                          }
237 tony          1.8  */
238 chuck         1.20     }
239 tony          1.8  }
240 kumpf         1.1  
241 tony          1.8  void CIMListenerService::shutdown()
242                    {
243                        PEG_METHOD_ENTER(TRC_LISTENER, "CIMListenerService::shutdown()");
244 kumpf         1.1  
245 tony          1.8      _dieNow = true;
246 a.arora       1.21     _monitor->tickle();
247 kumpf         1.1  
248 kumpf         1.4      PEG_METHOD_EXIT();
249 kumpf         1.1  }
250                    
251 tony          1.8  void CIMListenerService::resume()
252 kumpf         1.1  {
253 tony          1.8      PEG_METHOD_ENTER(TRC_LISTENER, "CIMListenerService::resume()");
254 kumpf         1.1  
255 tony          1.8      if(_acceptor!=NULL)
256                            _acceptor->reopenConnectionSocket();
257 kumpf         1.1  
258 kumpf         1.4      PEG_METHOD_EXIT();
259 kumpf         1.1  }
260                    
261 tony          1.8  void CIMListenerService::stopClientConnection()
262 kumpf         1.1  {
263 tony          1.8      PEG_METHOD_ENTER(TRC_LISTENER, "CIMListenerService::stopClientConnection()");
264 kumpf         1.1  
265 kumpf         1.10     // tell Monitor to stop listening for client connections
266 chuck         1.22     _monitor->stopListeningForConnections(true);
267 kumpf         1.10 
268                        //
269                        // Wait 150 milliseconds to allow time for the Monitor to stop
270                        // listening for client connections.
271                        //
272                        // This wait time is the timeout value for the select() call
273                        // in the Monitor's run() method (currently set to 100
274                        // milliseconds) plus a delta of 50 milliseconds.  The reason
275                        // for the wait here is to make sure that the Monitor entries
276                        // are updated before closing the connection sockets.
277                        //
278 a.arora       1.21     // pegasus_sleep(150); Not needed now due to the semaphore in the Monitor
279 david.dillard 1.31 
280 tony          1.8      if(_acceptor!=NULL)
281                        _acceptor->closeConnectionSocket();
282 kumpf         1.1  
283 kumpf         1.4      PEG_METHOD_EXIT();
284 kumpf         1.1  }
285                    
286 chuck         1.23 Uint32 CIMListenerService::getOutstandingRequestCount()
287                    {
288                        return _acceptor->getOutstandingRequestCount();
289                    }
290 tony          1.8  
291                    CIMListenerIndicationDispatcher* CIMListenerService::getIndicationDispatcher() const
292 kumpf         1.1  {
293 tony          1.8  	return _dispatcher;
294                    }
295                    void CIMListenerService::setIndicationDispatcher(CIMListenerIndicationDispatcher* dispatcher)
296                    {
297                    	_dispatcher = dispatcher;
298 kumpf         1.1  }
299                    
300 tony          1.8  PEGASUS_THREAD_RETURN PEGASUS_THREAD_CDECL CIMListenerService::_listener_routine(void *param)
301 kumpf         1.1  {
302 tony          1.8    CIMListenerService *svc = reinterpret_cast<CIMListenerService *>(param);
303 kumpf         1.1  
304 david.dillard 1.31   //svc->init(); bug 1394
305 chuck         1.20   while(!svc->terminated())
306                      {
307                    #if defined(PEGASUS_PLATFORM_DARWIN_PPC_GNU)
308                        pthread_testcancel();
309                    #endif
310                        svc->runForever();
311                      }
312 chuck         1.23 
313 chuck         1.20   delete svc;
314 tony          1.8  
315 chuck         1.20   return 0;
316 tony          1.8  }
317                    
318                    /////////////////////////////////////////////////////////////////////////////
319                    // CIMListenerRep
320                    /////////////////////////////////////////////////////////////////////////////
321                    class CIMListenerRep
322                    {
323                    public:
324                    	CIMListenerRep(Uint32 portNumber, SSLContext* sslContext=NULL);
325                      ~CIMListenerRep();
326                    
327                    	Uint32 getPortNumber() const;
328                    
329                    	SSLContext* getSSLContext() const;
330                    	void setSSLContext(SSLContext* sslContext);
331 david.dillard 1.31 
332 tony          1.8  	void start();
333                    	void stop();
334                    
335                    	Boolean isAlive();
336                    
337                    	Boolean addConsumer(CIMIndicationConsumer* consumer);
338                    	Boolean removeConsumer(CIMIndicationConsumer* consumer);
339                    
340                    private:
341 chuck         1.23   Boolean waitForPendingRequests(Uint32 shutdownTimeout);
342                    
343                      Uint32 _portNumber;
344                      SSLContext* _sslContext;
345 tony          1.8  
346                      CIMListenerIndicationDispatcher* _dispatcher;
347 chuck         1.23   ThreadPool* _thread_pool;
348 david.dillard 1.31   CIMListenerService* _svc;
349 chuck         1.20   Semaphore *_listener_sem;
350 tony          1.8  };
351                    
352                    CIMListenerRep::CIMListenerRep(Uint32 portNumber, SSLContext* sslContext)
353                    :_portNumber(portNumber)
354                    ,_sslContext(sslContext)
355                    ,_dispatcher(new CIMListenerIndicationDispatcher())
356                    ,_thread_pool(NULL)
357 chuck         1.20 ,_svc(NULL)
358                    ,_listener_sem(NULL)
359 tony          1.8  {
360                    }
361 david.dillard 1.31 
362 tony          1.8  CIMListenerRep::~CIMListenerRep()
363                    {
364 david.dillard 1.31     // if port is alive, clean up the port
365                        if (_thread_pool != 0)
366                        {
367                            // Block incoming export requests and unbind the port
368                            _svc->stopClientConnection();
369                    
370                            // Wait until pending export requests in the server are done.
371                            waitForPendingRequests(10);
372 chuck         1.23 
373 david.dillard 1.31         // Shutdown the CIMListenerService
374                            _svc->shutdown();
375                        }
376 chuck         1.20 
377                        delete _sslContext;
378                        delete _dispatcher;
379                        delete _thread_pool;
380                        delete _listener_sem;
381 tony          1.8  
382 chuck         1.20   // don't delete _svc, this is deleted by _listener_routine
383 tony          1.8  }
384                    
385                    Uint32 CIMListenerRep::getPortNumber() const
386                    {
387 david.dillard 1.31     return _portNumber;
388 tony          1.8  }
389 kumpf         1.1  
390 tony          1.8  SSLContext* CIMListenerRep::getSSLContext() const
391                    {
392 david.dillard 1.31     return _sslContext;
393 kumpf         1.1  }
394 david.dillard 1.31 
395 tony          1.8  void CIMListenerRep::setSSLContext(SSLContext* sslContext)
396                    {
397 david.dillard 1.31     delete _sslContext;
398                        _sslContext = sslContext;
399                    }
400 kumpf         1.1  
401 tony          1.8  void CIMListenerRep::start()
402 kumpf         1.1  {
403 david.dillard 1.31     // spawn a thread to do this
404                        if(_thread_pool==0)
405 chuck         1.20     {
406 david.dillard 1.31         AutoPtr<CIMListenerService> svc(new CIMListenerService(_portNumber,_sslContext));
407 chuck         1.20 
408 david.dillard 1.31         svc->setIndicationDispatcher(_dispatcher);
409                            svc->init();
410 chuck         1.20 
411 david.dillard 1.31         struct timeval deallocateWait = {15, 0};
412                            AutoPtr<ThreadPool> threadPool(new ThreadPool(0, "Listener", 0, 1, deallocateWait));
413                            AutoPtr<Semaphore> sem(new Semaphore(0));
414                            threadPool->allocate_and_awaken(svc.get(), CIMListenerService::_listener_routine, sem.get());
415                            Logger::put(Logger::STANDARD_LOG,System::CIMLISTENER, Logger::INFORMATION,
416                                            "CIMListener started");
417                    
418                            PEGASUS_STD(cerr) << "CIMlistener started" << PEGASUS_STD(endl);
419                    
420                            _svc = svc.release();
421                            _thread_pool = threadPool.release();
422                            _listener_sem = sem.release();
423                        }
424 tony          1.8  }
425 kumpf         1.1  
426 tony          1.8  void CIMListenerRep::stop()
427                    {
428 chuck         1.20   if(_thread_pool!=NULL)
429 david.dillard 1.31   {
430 chuck         1.20     //
431                        // Graceful shutdown of the listener service
432                        //
433                    
434                        // Block incoming export requests and unbind the port
435                        _svc->stopClientConnection();
436 chuck         1.23 
437                        // Wait until pending export requests in the server are done.
438 chuck         1.24     waitForPendingRequests(10);
439 david.dillard 1.31 
440 chuck         1.20     // Shutdown the CIMListenerService
441                        _svc->shutdown();
442                    
443                        // Wait for the _listener_routine thread to exit.
444                        // The thread could be delivering an export, so give it 3sec.
445                        // Note that _listener_routine deletes the CIMListenerService,
446                        // so no need to delete _svc.
447                        try
448                        {
449 david.dillard 1.31       _listener_sem->time_wait(3000);
450 chuck         1.20     }
451                        catch (TimeOut &)
452                        {
453                          // No need to do anything, the thread pool will be deleted below
454                          // to cancel the _listener_routine thread if it is still running.
455                        }
456                    
457                        delete _listener_sem;
458                        _listener_sem = NULL;
459 david.dillard 1.31 
460 chuck         1.20     // Delete the thread pool.  This cancels the listener thread if it is still
461                        // running.
462                        delete _thread_pool;
463                        _thread_pool = NULL;
464                    
465                        Logger::put(Logger::STANDARD_LOG,System::CIMLISTENER,
466                    		Logger::INFORMATION,
467                    		"CIMListener stopped");
468                      }
469 kumpf         1.1  }
470                    
471 tony          1.8  Boolean CIMListenerRep::isAlive()
472 kumpf         1.1  {
473 tony          1.8  	return (_thread_pool!=NULL)?true:false;
474                    }
475 kumpf         1.1  
476 tony          1.8  Boolean CIMListenerRep::addConsumer(CIMIndicationConsumer* consumer)
477                    {
478                    	return _dispatcher->addConsumer(consumer);
479                    }
480                    Boolean CIMListenerRep::removeConsumer(CIMIndicationConsumer* consumer)
481                    {
482                    	return _dispatcher->removeConsumer(consumer);
483                    }
484 kumpf         1.1  
485 chuck         1.23 Boolean CIMListenerRep::waitForPendingRequests(Uint32 shutdownTimeout)
486                    {
487                      // Wait for 10 sec max
488                      Uint32 reqCount;
489                      Uint32 countDown = shutdownTimeout * 10;
490                      for (; countDown > 0; countDown--)
491                      {
492                        reqCount = _svc->getOutstandingRequestCount();
493                        if (reqCount > 0)
494                          pegasus_sleep(100);
495                        else
496                          return true;
497                      }
498 david.dillard 1.31 
499 chuck         1.23   return false;
500 david.dillard 1.31 }
501 chuck         1.23 
502 tony          1.8  /////////////////////////////////////////////////////////////////////////////
503                    // CIMListener
504                    /////////////////////////////////////////////////////////////////////////////
505                    CIMListener::CIMListener(Uint32 portNumber, SSLContext* sslContext)
506                    :_rep(new CIMListenerRep(portNumber,sslContext))
507                    {
508                    }
509                    CIMListener::~CIMListener()
510                    {
511                    	if(_rep!=NULL)
512 tony          1.9  		delete static_cast<CIMListenerRep*>(_rep);
513 tony          1.8  	_rep=NULL;
514                    }
515 david.dillard 1.31 
516 tony          1.8  Uint32 CIMListener::getPortNumber() const
517                    {
518                    	return static_cast<CIMListenerRep*>(_rep)->getPortNumber();
519 kumpf         1.1  }
520                    
521 tony          1.8  SSLContext* CIMListener::getSSLContext() const
522                    {
523                    	return static_cast<CIMListenerRep*>(_rep)->getSSLContext();
524                    }
525                    void CIMListener::setSSLContext(SSLContext* sslContext)
526                    {
527                    	static_cast<CIMListenerRep*>(_rep)->setSSLContext(sslContext);
528                    }
529                    void CIMListener::start()
530 kumpf         1.1  {
531 tony          1.8  	static_cast<CIMListenerRep*>(_rep)->start();
532                    }
533                    void CIMListener::stop()
534                    {
535                    	static_cast<CIMListenerRep*>(_rep)->stop();
536                    }
537 kumpf         1.1  
538 tony          1.8  Boolean CIMListener::isAlive()
539                    {
540                    	return static_cast<CIMListenerRep*>(_rep)->isAlive();
541                    }
542 kumpf         1.1  
543 tony          1.8  Boolean CIMListener::addConsumer(CIMIndicationConsumer* consumer)
544                    {
545                    	return static_cast<CIMListenerRep*>(_rep)->addConsumer(consumer);
546                    }
547                    Boolean CIMListener::removeConsumer(CIMIndicationConsumer* consumer)
548                    {
549                    	return static_cast<CIMListenerRep*>(_rep)->removeConsumer(consumer);
550 kumpf         1.1  }
551                    
552                    PEGASUS_NAMESPACE_END

No CVS admin address has been configured
Powered by
ViewCVS 0.9.2