• Skip to content
  • Skip to link menu
Trinity API Reference
  • Trinity API Reference
  • tdecore
 

tdecore

  • tdecore
  • network
kresolvermanager.cpp
1/*
2 * Copyright (C) 2003-2005 Thiago Macieira <thiago.macieira@kdemail.net>
3 *
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining
6 * a copy of this software and associated documentation files (the
7 * "Software"), to deal in the Software without restriction, including
8 * without limitation the rights to use, copy, modify, merge, publish,
9 * distribute, sublicense, and/or sell copies of the Software, and to
10 * permit persons to whom the Software is furnished to do so, subject to
11 * the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included
14 * in all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 */
24
25#include "config.h"
26
27#include <sys/types.h>
28#include <netinet/in.h>
29#include <limits.h>
30#include <unistd.h> // only needed for pid_t
31
32#ifdef HAVE_RES_INIT
33# include <sys/stat.h>
34extern "C" {
35# include <arpa/nameser.h>
36}
37# include <time.h>
38# include <resolv.h>
39#endif
40
41#include <tqapplication.h>
42#include <tqstring.h>
43#include <tqcstring.h>
44#include <tqptrlist.h>
45#include <tqtimer.h>
46#include <tqmutex.h>
47#include <tqthread.h>
48#include <tqwaitcondition.h>
49#include <tqsemaphore.h>
50
51#include <kde_file.h>
52#include <kdebug.h>
53#include "kresolver.h"
54#include "kresolver_p.h"
55#include "kresolverworkerbase.h"
56
57namespace KNetwork
58{
59 namespace Internal
60 {
61 void initSrvWorker();
62 void initStandardWorkers();
63 }
64}
65
66using namespace KNetwork;
67using namespace KNetwork::Internal;
68
69/*
70 * Explanation on how the resolver system works
71
72 When KResolver::start is called, it calls KResolverManager::enqueue to add
73 an entry to the queue. KResolverManager::enqueue will verify the availability
74 of a worker thread: if one is available, it will dispatch the request to it.
75 If no threads are available, it will then decide whether to launch a thread
76 or to queue for the future.
77
78 (This process is achieved by always queueing the new request, starting a
79 new thread if necessary and then notifying of the availability of data
80 to all worker threads).
81
82 * Worker thread
83 A new thread, when started, will enter its event loop
84 immediately. That is, it'll first try to acquire new data to
85 process, which means it will lock and unlock the manager mutex in
86 the process.
87
88 If it finds no new data, it'll wait on the feedWorkers condition
89 for a certain maximum time. If that time expires and there's still
90 no data, the thread will exit, in order to save system resources.
91
92 If it finds data, however, it'll set up and call the worker class
93 that has been selected by the manager. Once that worker is done,
94 the thread releases the data through KResolverManager::releaseData.
95
96 * Data requesting/releasing
97 A worker thread always calls upon functions on the resolver manager
98 in order to acquire and release data.
99
100 When data is being requested, the KResolverManager::requestData
101 function will look the currentRequests list and return the first
102 Queued request it finds, while marking it to be InProgress.
103
104 When the worker class has returned, the worker thread will release
105 that data through the KResolverManager::releaseData function. If the
106 worker class has requested no further data (nRequests == 0), the
107 request's status is marked to be Done. It'll then look at the
108 requestor for that data: if it was requested by another worker,
109 it'll decrement the requests count for that one and add the results
110 to a list. And, finally, if the requests count for the requestor
111 becomes 0, it'll repeat this process for the requestor as well
112 (change status to Done, check for a requestor).
113 */
114
115namespace
116{
117
118/*
119 * This class is used to control the access to the
120 * system's resolver API.
121 *
122 * It is necessary to periodically poll /etc/resolv.conf and reload
123 * it if any changes are noticed. This class does exactly that.
124 *
125 * However, there's also the problem of reloading the structure while
126 * some threads are in progress. Therefore, we keep a usage reference count.
127 */
128class ResInitUsage
129{
130public:
131
132#ifdef HAVE_RES_INIT
133 time_t mTime;
134 int useCount;
135
136# ifndef RES_INIT_THREADSAFE
137 TQWaitCondition cond;
138 TQMutex mutex;
139# endif
140
141 bool shouldResInit()
142 {
143 // check if /etc/resolv.conf has changed
144 KDE_struct_stat st;
145 if (KDE_stat("/etc/resolv.conf", &st) != 0)
146 return false;
147
148 if (mTime != st.st_mtime)
149 {
150 kdDebug(179) << "shouldResInit: /etc/resolv.conf updated" << endl;
151 return true;
152 }
153 return false;
154 }
155
156 void callResInit()
157 {
158 if (mTime != 0)
159 {
160 // don't call it the first time
161 // let it be initialised naturally
162 kdDebug(179) << "callResInit: calling res_init()" << endl;
163 res_init();
164 }
165
166 KDE_struct_stat st;
167 if (KDE_stat("/etc/resolv.conf", &st) == 0)
168 mTime = st.st_mtime;
169 }
170
171 ResInitUsage()
172 : mTime(0), useCount(0)
173 { }
174
175 /*
176 * Marks the end of usage to the resolver tools
177 */
178 void release()
179 {
180# ifndef RES_INIT_THREADSAFE
181 TQMutexLocker locker(&mutex);
182 if (--useCount == 0)
183 {
184 if (shouldResInit())
185 callResInit();
186
187 // we've reached 0, wake up anyone that's waiting to call res_init
188 cond.wakeAll();
189 }
190# else
191 // do nothing
192# endif
193 }
194
195 /*
196 * Marks the beginning of usage of the resolver API
197 */
198 void acquire()
199 {
200# ifndef RES_INIT_THREADSAFE
201 mutex.lock();
202
203 if (shouldResInit())
204 {
205 if (useCount)
206 {
207 // other threads are already using the API, so wait till
208 // it's all clear
209 // the thread that emits this condition will also call res_init
210 //tqDebug("ResInitUsage: waiting for libresolv to be clear");
211 cond.wait(&mutex);
212 }
213 else
214 // we're clear
215 callResInit();
216 }
217 useCount++;
218 mutex.unlock();
219
220# else
221 if (shouldResInit())
222 callResInit();
223
224# endif
225 }
226
227#else
228 ResInitUsage()
229 { }
230
231 bool shouldResInit()
232 { return false; }
233
234 void acquire()
235 { }
236
237 void release()
238 { }
239#endif
240
241} resInit;
242
243} // anonymous namespace
244
245/*
246 * parameters
247 */
248// a thread will try maxThreadRetries to get data, waiting at most
249// maxThreadWaitTime milliseconds between each attempt. After that, it'll
250// exit
251static const int maxThreadWaitTime = 2000; // 2 seconds
252static const int maxThreads = 5;
253
254static pid_t pid; // FIXME -- disable when everything is ok
255
256KResolverThread::KResolverThread()
257 : data(0L)
258{
259}
260
261// remember! This function runs in a separate thread!
262void KResolverThread::run()
263{
264 // initialisation
265 // enter the loop already
266
267 //tqDebug("KResolverThread(thread %u/%p): started", pid, (void*)TQThread::currentThread());
268 KResolverManager::manager()->registerThread(this);
269 while (true)
270 {
271 data = KResolverManager::manager()->requestData(this, ::maxThreadWaitTime);
272 //tqDebug("KResolverThread(thread %u/%p) got data %p", KResolverManager::pid,
273 // (void*)TQThread::currentThread(), (void*)data);
274 if (data)
275 {
276 // yes, we got data
277 // process it!
278
279 // 1) set up
280 ;
281
282 // 2) run it
283 data->worker->run();
284
285 // 3) release data
286 KResolverManager::manager()->releaseData(this, data);
287
288 // now go back to the loop
289 }
290 else
291 break;
292 }
293
294 KResolverManager::manager()->unregisterThread(this);
295 //tqDebug("KResolverThread(thread %u/%p): exiting", pid, (void*)TQThread::currentThread());
296}
297
298bool KResolverThread::checkResolver()
299{
300 return resInit.shouldResInit();
301}
302
303void KResolverThread::acquireResolver()
304{
305#if defined(NEED_MUTEX) && !defined(Q_OS_FREEBSD)
306 getXXbyYYmutex.lock();
307#endif
308
309 resInit.acquire();
310}
311
312void KResolverThread::releaseResolver()
313{
314#if defined(NEED_MUTEX) && !defined(Q_OS_FREEBSD)
315 getXXbyYYmutex.unlock();
316#endif
317
318 resInit.release();
319}
320
321static KResolverManager *globalManager;
322
323KResolverManager* KResolverManager::manager()
324{
325 if (globalManager == 0L)
326 new KResolverManager();
327 return globalManager;
328}
329
330KResolverManager::KResolverManager()
331 : runningThreads(0), availableThreads(0)
332{
333 globalManager = this;
334 workers.setAutoDelete(true);
335 currentRequests.setAutoDelete(true);
336 initSrvWorker();
337 initStandardWorkers();
338
339 pid = getpid();
340}
341
342KResolverManager::~KResolverManager()
343{
344 // this should never be called
345
346 // kill off running threads
347 for (workers.first(); workers.current(); workers.next())
348 workers.current()->terminate();
349}
350
351void KResolverManager::registerThread(KResolverThread* )
352{
353}
354
355void KResolverManager::unregisterThread(KResolverThread*)
356{
357 runningThreads--;
358}
359
360// this function is called by KResolverThread::run
361RequestData* KResolverManager::requestData(KResolverThread *th, int maxWaitTime)
362{
364 // This function is called in a worker thread!!
366
367 // lock the mutex, so that the manager thread or other threads won't
368 // interfere.
369 TQMutexLocker locker(&mutex);
370 RequestData *data = findData(th);
371
372 if (data)
373 // it found something, that's good
374 return data;
375
376 // nope, nothing found; sleep for a while
377 availableThreads++;
378 feedWorkers.wait(&mutex, maxWaitTime);
379 availableThreads--;
380
381 data = findData(th);
382 return data;
383}
384
385RequestData* KResolverManager::findData(KResolverThread* th)
386{
388 // This function is called by @ref requestData above and must
389 // always be called with a locked mutex
391
392 // now find data to be processed
393 for (RequestData *curr = newRequests.first(); curr; curr = newRequests.next())
394 if (!curr->worker->m_finished)
395 {
396 // found one
397 if (curr->obj)
398 curr->obj->status = KResolver::InProgress;
399 curr->worker->th = th;
400
401 // move it to the currentRequests list
402 currentRequests.append(newRequests.take());
403
404 return curr;
405 }
406
407 // found nothing!
408 return 0L;
409}
410
411// this function is called by KResolverThread::run
412void KResolverManager::releaseData(KResolverThread *, RequestData* data)
413{
415 // This function is called in a worker thread!!
417
418 //tqDebug("KResolverManager::releaseData(%u/%p): %p has been released", pid,
419// (void*)TQThread::currentThread(), (void*)data);
420
421 if (data->obj)
422 {
423 data->obj->status = KResolver::PostProcessing;
424 }
425
426 data->worker->m_finished = true;
427 data->worker->th = 0L; // this releases the object
428
429 // handle finished requests
430 handleFinished();
431}
432
433// this function is called by KResolverManager::releaseData above
434void KResolverManager::handleFinished()
435{
436 bool redo = false;
437 TQPtrQueue<RequestData> doneRequests;
438
439 mutex.lock();
440
441 // loop over all items on the currently running list
442 // we loop from the last to the first so that we catch requests with "requestors" before
443 // we catch the requestor itself.
444 RequestData *curr = currentRequests.last();
445 while (curr)
446 {
447 if (curr->worker->th == 0L)
448 {
449 if (handleFinishedItem(curr))
450 {
451 doneRequests.enqueue(currentRequests.take());
452 if (curr->requestor &&
453 curr->requestor->nRequests == 0 &&
454 curr->requestor->worker->m_finished)
455 // there's a requestor that is now finished
456 redo = true;
457 }
458 }
459
460 curr = currentRequests.prev();
461 }
462
463 //tqDebug("KResolverManager::handleFinished(%u): %d requests to notify", pid, doneRequests.count());
464 while (RequestData *d = doneRequests.dequeue())
465 doNotifying(d);
466
467 mutex.unlock();
468
469 if (redo)
470 {
471 //tqDebug("KResolverManager::handleFinished(%u): restarting processing to catch requestor",
472 // pid);
473 handleFinished();
474 }
475}
476
477// This function is called by KResolverManager::handleFinished above
478bool KResolverManager::handleFinishedItem(RequestData* curr)
479
480{
481 // for all items that aren't currently running, remove from the list
482 // this includes all finished or cancelled requests
483
484 if (curr->worker->m_finished && curr->nRequests == 0)
485 {
486 // this one has finished
487 if (curr->obj)
488 curr->obj->status = KResolver::PostProcessing; // post-processing is run in doNotifying()
489
490 if (curr->requestor)
491 --curr->requestor->nRequests;
492
493 //tqDebug("KResolverManager::handleFinishedItem(%u): removing %p since it's done",
494 // pid, (void*)curr);
495 return true;
496 }
497 return false;
498}
499
500
501
502void KResolverManager::registerNewWorker(KResolverWorkerFactoryBase *factory)
503{
504 workerFactories.append(factory);
505}
506
507KResolverWorkerBase* KResolverManager::findWorker(KResolverPrivate* p)
508{
510 // this function can be called on any user thread
512
513 // this function is called with an unlocked mutex and it's expected to be
514 // thread-safe!
515 // but the factory list is expected not to be changed asynchronously
516
517 // This function is responsible for finding a suitable worker for the given
518 // input. That means we have to do a costly operation to create each worker
519 // class and call their preprocessing functions. The first one that
520 // says they can process (i.e., preprocess() returns true) will get the job.
521
522 KResolverWorkerBase *worker;
523 for (KResolverWorkerFactoryBase *factory = workerFactories.first(); factory;
524 factory = workerFactories.next())
525 {
526 worker = factory->create();
527
528 // set up the data the worker needs to preprocess
529 worker->input = &p->input;
530
531 if (worker->preprocess())
532 {
533 // good, this one says it can process
534 if (worker->m_finished)
535 p->status = KResolver::PostProcessing;
536 else
537 p->status = KResolver::Queued;
538 return worker;
539 }
540
541 // no, try again
542 delete worker;
543 }
544
545 // found no worker
546 return 0L;
547}
548
549void KResolverManager::doNotifying(RequestData *p)
550{
552 // This function may be called on any thread
553 // any thread at all: user threads, GUI thread, manager thread or worker thread
555
556 // Notification and finalisation
557 //
558 // Once a request has finished the normal processing, we call the
559 // post processing function.
560 //
561 // After that is done, we will consolidate all results in the object's
562 // KResolverResults and then post an event indicating that the signal
563 // be emitted
564 //
565 // In case we detect that the object is waiting for completion, we do not
566 // post the event, for KResolver::wait will take care of emitting the
567 // signal.
568 //
569 // Once we release the mutex on the object, we may no longer reference it
570 // for it might have been deleted.
571
572 // "User" objects are those that are not created by the manager. Note that
573 // objects created by worker threads are considered "user" objects. Objects
574 // created by the manager are those created for KResolver::resolveAsync.
575 // We should delete them.
576
577 if (p->obj)
578 {
579 // lock the object
580 p->obj->mutex.lock();
581 KResolver* parent = p->obj->parent; // is 0 for non-"user" objects
582 KResolverResults& r = p->obj->results;
583
584 if (p->obj->status == KResolver::Canceled)
585 {
586 p->obj->status = KResolver::Canceled;
587 p->obj->errorcode = KResolver::Canceled;
588 p->obj->syserror = 0;
589 r.setError(KResolver::Canceled, 0);
590 }
591 else if (p->worker)
592 {
593 // post processing
594 p->worker->postprocess(); // ignore the result
595
596 // copy the results from the worker thread to the final
597 // object
598 r = p->worker->results;
599
600 // reset address
601 r.setAddress(p->input->node, p->input->service);
602
603 //tqDebug("KResolverManager::doNotifying(%u/%p): for %p whose status is %d and has %d results",
604 //pid, (void*)TQThread::currentThread(), (void*)p, p->obj->status, r.count());
605
606 p->obj->errorcode = r.error();
607 p->obj->syserror = r.systemError();
608 p->obj->status = !r.isEmpty() ?
609 KResolver::Success : KResolver::Failed;
610 }
611 else
612 {
613 r.empty();
614 r.setError(p->obj->errorcode, p->obj->syserror);
615 }
616
617 // check whether there's someone waiting
618 if (!p->obj->waiting && parent)
619 // no, so we must post an event requesting that the signal be emitted
620 // sorry for the C-style cast, but neither static nor reintepret cast work
621 // here; I'd have to do two casts
622 TQApplication::postEvent(parent, new TQEvent((TQEvent::Type)(ResolutionCompleted)));
623
624 // release the mutex
625 p->obj->mutex.unlock();
626 }
627 else
628 {
629 // there's no object!
630 if (p->worker)
631 p->worker->postprocess();
632 }
633
634 delete p->worker;
635
636 // ignore p->requestor and p->nRequests
637 // they have been dealt with by the main loop
638
639 delete p;
640
641 // notify any objects waiting in KResolver::wait
642 notifyWaiters.wakeAll();
643}
644
645// enqueue a new request
646// this function is called from KResolver::start and
647// from KResolverWorkerBase::enqueue
648void KResolverManager::enqueue(KResolver *obj, RequestData *requestor)
649{
650 RequestData *newrequest = new RequestData;
651 newrequest->nRequests = 0;
652 newrequest->obj = obj->d;
653 newrequest->input = &obj->d->input;
654 newrequest->requestor = requestor;
655
656 // when processing a new request, find the most
657 // suitable worker
658 if ((newrequest->worker = findWorker(obj->d)) == 0L)
659 {
660 // oops, problem
661 // cannot find a worker class for this guy
662 obj->d->status = KResolver::Failed;
663 obj->d->errorcode = KResolver::UnsupportedFamily;
664 obj->d->syserror = 0;
665
666 doNotifying(newrequest);
667 return;
668 }
669
670 // no, queue it
671 // p->status was set in findWorker!
672 if (requestor)
673 requestor->nRequests++;
674
675 if (!newrequest->worker->m_finished)
676 dispatch(newrequest);
677 else if (newrequest->nRequests > 0)
678 {
679 mutex.lock();
680 currentRequests.append(newrequest);
681 mutex.unlock();
682 }
683 else
684 // already done
685 doNotifying(newrequest);
686}
687
688// a new request has been created
689// dispatch it
690void KResolverManager::dispatch(RequestData *data)
691{
692 // As stated in the beginning of the file, this function
693 // is supposed to verify the availability of threads, start
694 // any if necessary
695
696 TQMutexLocker locker(&mutex);
697
698 // add to the queue
699 newRequests.append(data);
700
701 // check if we need to start a new thread
702 //
703 // we depend on the variables availableThreads and runningThreads to
704 // know if we are supposed to start any threads:
705 // - if availableThreads > 0, then there is at least one thread waiting,
706 // blocked in KResolverManager::requestData. It can't unblock
707 // while we are holding the mutex locked, therefore we are sure that
708 // our event will be handled
709 // - if availableThreads == 0:
710 // - if runningThreads < maxThreads
711 // we will start a new thread, which will certainly block in
712 // KResolverManager::requestData because we are holding the mutex locked
713 // - if runningThreads == maxThreads
714 // This situation generally means that we have already maxThreads running
715 // and that all of them are processing. We will not start any new threads,
716 // but will instead wait for one to finish processing and request new data
717 //
718 // There's a possible race condition here, which goes unhandled: if one of
719 // threads has timed out waiting for new data and is in the process of
720 // exiting. In that case, availableThreads == 0 and runningThreads will not
721 // have decremented yet. This means that we will not start a new thread
722 // that we could have. However, since there are other threads working, our
723 // event should be handled soon.
724 // It won't be handled if and only if ALL threads are in the process of
725 // exiting. That situation is EXTREMELY unlikely and is not handled either.
726 //
727 if (availableThreads == 0 && runningThreads < maxThreads)
728 {
729 // yes, a new thread should be started
730
731 // find if there's a finished one
732 KResolverThread *th = workers.first();
733 while (th && th->running())
734 th = workers.next();
735
736 if (th == 0L)
737 // no, create one
738 th = new KResolverThread;
739 else
740 workers.take();
741
742 th->start();
743 workers.append(th);
744 runningThreads++;
745 }
746
747 feedWorkers.wakeAll();
748
749 // clean up idle threads
750 workers.first();
751 while (workers.current())
752 {
753 if (!workers.current()->running())
754 workers.remove();
755 else
756 workers.next();
757 }
758}
759
760// this function is called by KResolverManager::dequeue
761bool KResolverManager::dequeueNew(KResolver* obj)
762{
763 // This function must be called with a locked mutex
764 // Deadlock warning:
765 // always lock the global mutex first if both mutexes must be locked
766
767 KResolverPrivate *d = obj->d;
768
769 // check if it's in the new request list
770 RequestData *curr = newRequests.first();
771 while (curr)
772 if (curr->obj == d)
773 {
774 // yes, this object is still in the list
775 // but it has never been processed
776 d->status = KResolver::Canceled;
777 d->errorcode = KResolver::Canceled;
778 d->syserror = 0;
779 newRequests.take();
780
781 delete curr->worker;
782 delete curr;
783
784 return true;
785 }
786 else
787 curr = newRequests.next();
788
789 // check if it's running
790 curr = currentRequests.first();
791 while (curr)
792 if (curr->obj == d)
793 {
794 // it's running. We cannot simply take it out of the list.
795 // it will be handled when the thread that is working on it finishes
796 d->mutex.lock();
797
798 d->status = KResolver::Canceled;
799 d->errorcode = KResolver::Canceled;
800 d->syserror = 0;
801
802 // disengage from the running threads
803 curr->obj = 0L;
804 curr->input = 0L;
805 if (curr->worker)
806 curr->worker->input = 0L;
807
808 d->mutex.unlock();
809 }
810 else
811 curr = currentRequests.next();
812
813 return false;
814}
815
816// this function is called by KResolver::cancel
817// it's expected to be thread-safe
818void KResolverManager::dequeue(KResolver *obj)
819{
820 TQMutexLocker locker(&mutex);
821 dequeueNew(obj);
822}
KNetwork::KResolverResults
Name and service resolution results.
Definition: kresolver.h:198
KNetwork::KResolverResults::setAddress
void setAddress(const TQString &host, const TQString &service)
Sets the new nodename and service name.
Definition: kresolver.cpp:278
KNetwork::KResolverResults::systemError
int systemError() const
Retrieves the system error code, if any.
Definition: kresolver.cpp:253
KNetwork::KResolverResults::error
int error() const
Retrieves the error code associated with this resolution.
Definition: kresolver.cpp:247
KNetwork::KResolverResults::setError
void setError(int errorcode, int systemerror=0)
Sets the error codes.
Definition: kresolver.cpp:259
KNetwork::KResolver
Name and service resolution class.
Definition: kresolver.h:296
endl
kndbgstream & endl(kndbgstream &s)
Does nothing.
Definition: kdebug.h:583
KNetwork
A namespace to store all networking-related (socket) classes.
Definition: kbufferedsocket.h:36
KStdAction::redo
TDEAction * redo(const TQObject *recvr, const char *slot, TDEActionCollection *parent, const char *name=0)

tdecore

Skip menu "tdecore"
  • Main Page
  • Modules
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

tdecore

Skip menu "tdecore"
  • arts
  • dcop
  • dnssd
  • interfaces
  •   kspeech
  •     interface
  •     library
  •   tdetexteditor
  • kate
  • kded
  • kdoctools
  • kimgio
  • kjs
  • libtdemid
  • libtdescreensaver
  • tdeabc
  • tdecmshell
  • tdecore
  • tdefx
  • tdehtml
  • tdeinit
  • tdeio
  •   bookmarks
  •   httpfilter
  •   kpasswdserver
  •   kssl
  •   tdefile
  •   tdeio
  •   tdeioexec
  • tdeioslave
  •   http
  • tdemdi
  •   tdemdi
  • tdenewstuff
  • tdeparts
  • tdeprint
  • tderandr
  • tderesources
  • tdespell2
  • tdesu
  • tdeui
  • tdeunittest
  • tdeutils
  • tdewallet
Generated for tdecore by doxygen 1.9.4
This website is maintained by Timothy Pearson.