source: trunk/libtransmission/platform.c @ 2323

Last change on this file since 2323 was 2323, checked in by titer, 14 years ago

Adds BeOS tr_condBroadcast implementation

  • Property svn:keywords set to Date Rev Author Id
File size: 18.9 KB
Line 
1/******************************************************************************
2 * $Id: platform.c 2323 2007-07-10 14:00:20Z titer $
3 *
4 * Copyright (c) 2005 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#ifdef SYS_BEOS
26  #include <fs_info.h>
27  #include <FindDirectory.h>
28#endif
29#include <sys/types.h>
30#include <dirent.h>
31
32#include "transmission.h"
33
34#if !defined( SYS_BEOS ) && !defined( __AMIGAOS4__ )
35
36#include <pwd.h>
37
38const char *
39tr_getHomeDirectory( void )
40{
41    static char     homeDirectory[MAX_PATH_LENGTH];
42    static int      init = 0;
43    char          * envHome;
44    struct passwd * pw;
45
46    if( init )
47    {
48        return homeDirectory;
49    }
50
51    envHome = getenv( "HOME" );
52    if( NULL == envHome )
53    {
54        pw = getpwuid( getuid() );
55        endpwent();
56        if( NULL == pw )
57        {
58            /* XXX need to handle this case */
59            return NULL;
60        }
61        envHome = pw->pw_dir;
62    }
63
64    snprintf( homeDirectory, MAX_PATH_LENGTH, "%s", envHome );
65    init = 1;
66
67    return homeDirectory;
68}
69
70#else
71
72const char *
73tr_getHomeDirectory( void )
74{
75    /* XXX */
76    return "";
77}
78
79#endif /* !SYS_BEOS && !__AMIGAOS4__ */
80
81static void
82tr_migrateResume( const char *oldDirectory, const char *newDirectory )
83{
84    DIR * dirh = opendir( oldDirectory );
85
86    if( dirh != NULL )
87    {
88        struct dirent * dirp;
89
90        while( ( dirp = readdir( dirh ) ) )
91        {
92            if( !strncmp( "resume.", dirp->d_name, 7 ) )
93            {
94                char o[MAX_PATH_LENGTH];
95                char n[MAX_PATH_LENGTH];
96                tr_buildPath( o, sizeof(o), oldDirectory, dirp->d_name, NULL );
97                tr_buildPath( n, sizeof(n), newDirectory, dirp->d_name, NULL );
98                rename( o, n );
99            }
100        }
101
102        closedir( dirh );
103    }
104}
105
106const char *
107tr_getPrefsDirectory( void )
108{
109    static char   buf[MAX_PATH_LENGTH];
110    static int    init = 0;
111    static size_t buflen = sizeof(buf);
112    const char* h;
113
114    if( init )
115        return buf;
116
117    h = tr_getHomeDirectory();
118#ifdef SYS_BEOS
119    find_directory( B_USER_SETTINGS_DIRECTORY,
120                    dev_for_path("/boot"), true, buf, buflen );
121    strcat( buf, "/Transmission" );
122#elif defined( SYS_DARWIN )
123    tr_buildPath ( buf, buflen, h,
124                  "Library", "Application Support", "Transmission", NULL );
125#elif defined(__AMIGAOS4__)
126    snprintf( buf, buflen, "PROGDIR:.transmission" );
127#else
128    tr_buildPath ( buf, buflen, h, ".transmission", NULL );
129#endif
130
131    tr_mkdir( buf );
132    init = 1;
133
134#ifdef SYS_DARWIN
135    char old[MAX_PATH_LENGTH];
136    tr_buildPath ( old, sizeof(old), h, ".transmission", NULL );
137    tr_migrateResume( old, buf );
138    rmdir( old );
139#endif
140
141    return buf;
142}
143
144const char *
145tr_getCacheDirectory( void )
146{
147    static char buf[MAX_PATH_LENGTH];
148    static int  init = 0;
149    static const size_t buflen = sizeof(buf);
150    const char * p;
151
152    if( init )
153        return buf;
154
155    p = tr_getPrefsDirectory();
156#ifdef SYS_BEOS
157    tr_buildPath( buf, buflen, p, "Cache", NULL );
158#elif defined( SYS_DARWIN )
159    tr_buildPath( buf, buflen, tr_getHomeDirectory(),
160                  "Library", "Caches", "Transmission", NULL );
161#else
162    tr_buildPath( buf, buflen, p, "cache", NULL );
163#endif
164
165    tr_mkdir( buf );
166    init = 1;
167
168    if( strcmp( p, buf ) )
169        tr_migrateResume( p, buf );
170
171    return buf;
172}
173
174const char *
175tr_getTorrentsDirectory( void )
176{
177    static char buf[MAX_PATH_LENGTH];
178    static int  init = 0;
179    static const size_t buflen = sizeof(buf);
180    const char * p;
181
182    if( init )
183        return buf;
184
185    p = tr_getPrefsDirectory ();
186
187#ifdef SYS_BEOS
188    tr_buildPath( buf, buflen, p, "Torrents", NULL );
189#elif defined( SYS_DARWIN )
190    tr_buildPath( buf, buflen, p, "Torrents", NULL );
191#else
192    tr_buildPath( buf, buflen, p, "torrents", NULL );
193#endif
194
195    tr_mkdir( buf );
196    init = 1;
197    return buf;
198}
199
200static void ThreadFunc( void * _t )
201{
202    tr_thread_t * t = _t;
203    char* name = tr_strdup( t->name );
204
205#ifdef SYS_BEOS
206    /* This is required because on BeOS, SIGINT is sent to each thread,
207       which kills them not nicely */
208    signal( SIGINT, SIG_IGN );
209#endif
210
211    tr_dbg( "Thread '%s' started", name );
212    t->func( t->arg );
213    tr_dbg( "Thread '%s' exited", name );
214    tr_free( name );
215}
216
217void tr_threadCreate( tr_thread_t * t,
218                      void (*func)(void *), void * arg,
219                      const char * name )
220{
221    t->func = func;
222    t->arg  = arg;
223    t->name = tr_strdup( name );
224#ifdef SYS_BEOS
225    t->thread = spawn_thread( (void *) ThreadFunc, name,
226                              B_NORMAL_PRIORITY, t );
227    resume_thread( t->thread );
228#else
229    pthread_create( &t->thread, NULL, (void *) ThreadFunc, t );
230#endif
231}
232
233const tr_thread_t THREAD_EMPTY = { NULL, NULL, NULL, 0 };
234
235void tr_threadJoin( tr_thread_t * t )
236{
237    if( t->func != NULL )
238    {
239#ifdef SYS_BEOS
240        long exit;
241        wait_for_thread( t->thread, &exit );
242#else
243        pthread_join( t->thread, NULL );
244#endif
245        tr_dbg( "Thread '%s' joined", t->name );
246        tr_free( t->name );
247        t->name = NULL;
248        t->func = NULL;
249    }
250}
251
252void tr_lockInit( tr_lock_t * l )
253{
254#ifdef SYS_BEOS
255    *l = create_sem( 1, "" );
256#else
257    pthread_mutex_init( l, NULL );
258#endif
259}
260
261void tr_lockClose( tr_lock_t * l )
262{
263#ifdef SYS_BEOS
264    delete_sem( *l );
265#else
266    pthread_mutex_destroy( l );
267#endif
268}
269
270int tr_lockTryLock( tr_lock_t * l )
271{
272#ifdef SYS_BEOS
273    return acquire_sem_etc( *l, 1, B_RELATIVE_TIMEOUT, 0 );
274#else
275    /* success on zero! */
276    return pthread_mutex_trylock( l );
277#endif
278}
279
280void tr_lockLock( tr_lock_t * l )
281{
282#ifdef SYS_BEOS
283    acquire_sem( *l );
284#else
285    pthread_mutex_lock( l );
286#endif
287}
288
289void tr_lockUnlock( tr_lock_t * l )
290{
291#ifdef SYS_BEOS
292    release_sem( *l );
293#else
294    pthread_mutex_unlock( l );
295#endif
296}
297
298
299void tr_condInit( tr_cond_t * c )
300{
301#ifdef SYS_BEOS
302    c->sem = create_sem( 1, "" );
303    c->start = 0;
304    c->end = 0;
305#else
306    pthread_cond_init( c, NULL );
307#endif
308}
309
310void tr_condWait( tr_cond_t * c, tr_lock_t * l )
311{
312#ifdef SYS_BEOS
313    /* Keep track of that thread */
314    acquire_sem( c->sem );
315    c->threads[c->end] = find_thread( NULL );
316    c->end = ( c->end + 1 ) % BEOS_MAX_THREADS;
317    assert( c->end != c->start ); /* We hit BEOS_MAX_THREADS, arggh */
318    release_sem( c->sem );
319
320    release_sem( *l );
321    suspend_thread( find_thread( NULL ) ); /* Wait for signal */
322    acquire_sem( *l );
323#else
324    pthread_cond_wait( c, l );
325#endif
326}
327
328#ifdef SYS_BEOS
329static int condTrySignal( tr_cond_t * c )
330{
331    if( c->start == c->end )
332        return 1;
333
334    for( ;; )
335    {
336        thread_info info;
337        get_thread_info( c->threads[c->start], &info );
338        if( info.state == B_THREAD_SUSPENDED )
339        {
340            resume_thread( c->threads[c->start] );
341            c->start = ( c->start + 1 ) % BEOS_MAX_THREADS;
342            break;
343        }
344        /* The thread is not suspended yet, which can happen since
345         * tr_condWait does not atomically suspends after releasing
346         * the semaphore. Wait a bit and try again. */
347        snooze( 5000 );
348    }
349    return 0;
350}
351#endif
352void tr_condSignal( tr_cond_t * c )
353{
354#ifdef SYS_BEOS
355    acquire_sem( c->sem );
356    condTrySignal( c );
357    release_sem( c->sem );
358#else
359    pthread_cond_signal( c );
360#endif
361}
362void tr_condBroadcast( tr_cond_t * c )
363{
364#ifdef SYS_BEOS
365    acquire_sem( c->sem );
366    while( !condTrySignal( c ) );
367    release_sem( c->sem );
368#else
369    pthread_cond_broadcast( c );
370#endif
371}
372
373void tr_condClose( tr_cond_t * c )
374{
375#ifdef SYS_BEOS
376    delete_sem( c->sem );
377#else
378    pthread_cond_destroy( c );
379#endif
380}
381
382
383#if defined( BSD )
384
385#include <sys/sysctl.h>
386#include <net/route.h>
387
388static uint8_t *
389getroute( int * buflen );
390static int
391parseroutes( uint8_t * buf, int len, struct in_addr * addr );
392
393int
394tr_getDefaultRoute( struct in_addr * addr )
395{
396    uint8_t * buf;
397    int len;
398
399    buf = getroute( &len );
400    if( NULL == buf )
401    {
402        tr_err( "failed to get default route (BSD)" );
403        return 1;
404    }
405
406    len = parseroutes( buf, len, addr );
407    free( buf );
408
409    return len;
410}
411
412#ifndef SA_SIZE
413#define ROUNDUP( a, size ) \
414    ( ( (a) & ( (size) - 1 ) ) ? ( 1 + ( (a) | ( (size) - 1 ) ) ) : (a) )
415#define SA_SIZE( sap ) \
416    ( sap->sa_len ? ROUNDUP( (sap)->sa_len, sizeof( u_long ) ) : \
417                    sizeof( u_long ) )
418#endif /* !SA_SIZE */
419#define NEXT_SA( sap ) \
420    (struct sockaddr *) ( (caddr_t) (sap) + ( SA_SIZE( (sap) ) ) )
421
422static uint8_t *
423getroute( int * buflen )
424{
425    int     mib[6];
426    size_t  len;
427    uint8_t * buf;
428
429    mib[0] = CTL_NET;
430    mib[1] = PF_ROUTE;
431    mib[2] = 0;
432    mib[3] = AF_INET;
433    mib[4] = NET_RT_FLAGS;
434    mib[5] = RTF_GATEWAY;
435
436    if( sysctl( mib, 6, NULL, &len, NULL, 0 ) )
437    {
438        if( ENOENT != errno )
439        {
440            tr_err( "sysctl net.route.0.inet.flags.gateway failed (%s)",
441                    strerror( errno ) );
442        }
443        *buflen = 0;
444        return NULL;
445    }
446
447    buf = malloc( len );
448    if( NULL == buf )
449    {
450        *buflen = 0;
451        return NULL;
452    }
453
454    if( sysctl( mib, 6, buf, &len, NULL, 0 ) )
455    {
456        tr_err( "sysctl net.route.0.inet.flags.gateway failed (%s)",
457                strerror( errno ) );
458        free( buf );
459        *buflen = 0;
460        return NULL;
461    }
462
463    *buflen = len;
464
465    return buf;
466}
467
468static int
469parseroutes( uint8_t * buf, int len, struct in_addr * addr )
470{
471    uint8_t            * end;
472    struct rt_msghdr   * rtm;
473    struct sockaddr    * sa;
474    struct sockaddr_in * sin;
475    int                  ii;
476    struct in_addr       dest, gw;
477
478    end = buf + len;
479    while( end > buf + sizeof( *rtm ) )
480    {
481        rtm = (struct rt_msghdr *) buf;
482        buf += rtm->rtm_msglen;
483        if( end >= buf )
484        {
485            dest.s_addr = INADDR_NONE;
486            gw.s_addr   = INADDR_NONE;
487            sa = (struct sockaddr *) ( rtm + 1 );
488
489            for( ii = 0; ii < RTAX_MAX && (uint8_t *) sa < buf; ii++ )
490            {
491                if( buf < (uint8_t *) NEXT_SA( sa ) )
492                {
493                    break;
494                }
495
496                if( rtm->rtm_addrs & ( 1 << ii ) )
497                {
498                    if( AF_INET == sa->sa_family )
499                    {
500                        sin = (struct sockaddr_in *) sa;
501                        switch( ii )
502                        {
503                            case RTAX_DST:
504                                dest = sin->sin_addr;
505                                break;
506                            case RTAX_GATEWAY:
507                                gw = sin->sin_addr;
508                                break;
509                        }
510                    }
511                    sa = NEXT_SA( sa );
512                }
513            }
514
515            if( INADDR_ANY == dest.s_addr && INADDR_NONE != gw.s_addr )
516            {
517                *addr = gw;
518                return 0;
519            }
520        }
521    }
522
523    return 1;
524}
525
526#elif defined( linux ) || defined( __linux ) || defined( __linux__ )
527
528#include <linux/types.h>
529#include <linux/netlink.h>
530#include <linux/rtnetlink.h>
531
532#define SEQNUM 195909
533
534static int
535getsock( void );
536static uint8_t *
537getroute( int fd, unsigned int * buflen );
538static int
539parseroutes( uint8_t * buf, unsigned int len, struct in_addr * addr );
540
541int
542tr_getDefaultRoute( struct in_addr * addr )
543{
544    int fd, ret;
545    unsigned int len;
546    uint8_t * buf;
547
548    ret = 1;
549    fd = getsock();
550    if( 0 <= fd )
551    {
552        while( ret )
553        {
554            buf = getroute( fd, &len );
555            if( NULL == buf )
556            {
557                break;
558            }
559            ret = parseroutes( buf, len, addr );
560            free( buf );
561        }
562        close( fd );
563    }
564
565    if( ret )
566    {
567        tr_err( "failed to get default route (Linux)" );
568    }
569
570    return ret;
571}
572
573static int
574getsock( void )
575{
576    int fd, flags;
577    struct
578    {
579        struct nlmsghdr nlh;
580        struct rtgenmsg rtg;
581    } req;
582    struct sockaddr_nl snl;
583
584    fd = socket( PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE );
585    if( 0 > fd )
586    {
587        tr_err( "failed to create routing socket (%s)", strerror( errno ) );
588        return -1;
589    }
590
591    flags = fcntl( fd, F_GETFL );
592    if( 0 > flags || 0 > fcntl( fd, F_SETFL, O_NONBLOCK | flags ) )
593    {
594        tr_err( "failed to set socket nonblocking (%s)", strerror( errno ) );
595        close( fd );
596        return -1;
597    }
598
599    bzero( &snl, sizeof( snl ) );
600    snl.nl_family = AF_NETLINK;
601
602    bzero( &req, sizeof( req ) );
603    req.nlh.nlmsg_len = NLMSG_LENGTH( sizeof( req.rtg ) );
604    req.nlh.nlmsg_type = RTM_GETROUTE;
605    req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
606    req.nlh.nlmsg_seq = SEQNUM;
607    req.nlh.nlmsg_pid = 0;
608    req.rtg.rtgen_family = AF_INET;
609
610    if( 0 > sendto( fd, &req, sizeof( req ), 0,
611                    (struct sockaddr *) &snl, sizeof( snl ) ) )
612    {
613        tr_err( "failed to write to routing socket (%s)", strerror( errno ) );
614        close( fd );
615        return -1;
616    }
617
618    return fd;
619}
620
621static uint8_t *
622getroute( int fd, unsigned int * buflen )
623{
624    void             * buf;
625    unsigned int       len;
626    ssize_t            res;
627    struct sockaddr_nl snl;
628    socklen_t          slen;
629
630    len = 8192;
631    buf = calloc( 1, len );
632    if( NULL == buf )
633    {
634        *buflen = 0;
635        return NULL;
636    }
637
638    for( ;; )
639    {
640        bzero( &snl, sizeof( snl ) );
641        slen = sizeof( snl );
642        res = recvfrom( fd, buf, len, 0, (struct sockaddr *) &snl, &slen );
643        if( 0 > res )
644        {
645            if( EAGAIN != errno )
646            {
647                tr_err( "failed to read from routing socket (%s)",
648                        strerror( errno ) );
649            }
650            free( buf );
651            *buflen = 0;
652            return NULL;
653        }
654        if( slen < sizeof( snl ) || AF_NETLINK != snl.nl_family )
655        {
656            tr_err( "bad address" );
657            free( buf );
658            *buflen = 0;
659            return NULL;
660        }
661
662        if( 0 == snl.nl_pid )
663        {
664            break;
665        }
666    }
667
668    *buflen = res;
669
670    return buf;
671}
672
673static int
674parseroutes( uint8_t * buf, unsigned int len, struct in_addr * addr )
675{
676    struct nlmsghdr * nlm;
677    struct nlmsgerr * nle;
678    struct rtmsg    * rtm;
679    struct rtattr   * rta;
680    int               rtalen;
681    struct in_addr    gw, dst;
682
683    nlm = ( struct nlmsghdr * ) buf;
684    while( NLMSG_OK( nlm, len ) )
685    {
686        gw.s_addr = INADDR_ANY;
687        dst.s_addr = INADDR_ANY;
688        if( NLMSG_ERROR == nlm->nlmsg_type )
689        {
690            nle = (struct nlmsgerr *) NLMSG_DATA( nlm );
691            if( NLMSG_LENGTH( NLMSG_ALIGN( sizeof( struct nlmsgerr ) ) ) >
692                nlm->nlmsg_len )
693            {
694                tr_err( "truncated netlink error" );
695            }
696            else
697            {
698                tr_err( "netlink error (%s)", strerror( nle->error ) );
699            }
700            return 1;
701        }
702        else if( RTM_NEWROUTE == nlm->nlmsg_type && SEQNUM == nlm->nlmsg_seq &&
703                 getpid() == (pid_t) nlm->nlmsg_pid &&
704                 NLMSG_LENGTH( sizeof( struct rtmsg ) ) <= nlm->nlmsg_len )
705        {
706            rtm = NLMSG_DATA( nlm );
707            rta = RTM_RTA( rtm );
708            rtalen = RTM_PAYLOAD( nlm );
709
710            while( RTA_OK( rta, rtalen ) )
711            {
712                if( sizeof( struct in_addr ) <= RTA_PAYLOAD( rta ) )
713                {
714                    switch( rta->rta_type )
715                    {
716                        case RTA_GATEWAY:
717                            memcpy( &gw, RTA_DATA( rta ), sizeof( gw ) );
718                            break;
719                        case RTA_DST:
720                            memcpy( &dst, RTA_DATA( rta ), sizeof( dst ) );
721                            break;
722                    }
723                }
724                rta = RTA_NEXT( rta, rtalen );
725            }
726        }
727
728        if( INADDR_NONE != gw.s_addr && INADDR_ANY != gw.s_addr &&
729            INADDR_ANY == dst.s_addr )
730        {
731            *addr = gw;
732            return 0;
733        }
734
735        nlm = NLMSG_NEXT( nlm, len );
736    }
737
738    return 1;
739}
740
741#else /* not BSD or Linux */
742
743int
744tr_getDefaultRoute( struct in_addr * addr UNUSED )
745{
746    tr_inf( "don't know how to get default route on this platform" );
747    return 1;
748}
749
750#endif
751
752/***
753****
754***/
755
756static void
757tr_rwSignal( tr_rwlock_t * rw )
758{
759  if ( rw->wantToWrite )
760    tr_condSignal( &rw->writeCond );
761  else if ( rw->wantToRead )
762    tr_condBroadcast( &rw->readCond );
763}
764
765void
766tr_rwInit ( tr_rwlock_t * rw )
767{
768    memset( rw, 0, sizeof(tr_rwlock_t) );
769    tr_lockInit( &rw->lock );
770    tr_condInit( &rw->readCond );
771    tr_condInit( &rw->writeCond );
772}
773
774void
775tr_rwReaderLock( tr_rwlock_t * rw )
776{
777    tr_lockLock( &rw->lock );
778    rw->wantToRead++;
779    while( rw->haveWriter || rw->wantToWrite )
780        tr_condWait( &rw->readCond, &rw->lock );
781    rw->wantToRead--;
782    rw->readCount++;
783    tr_lockUnlock( &rw->lock );
784}
785
786int
787tr_rwReaderTrylock( tr_rwlock_t * rw )
788{
789    int ret = FALSE;
790    tr_lockLock( &rw->lock );
791    if ( !rw->haveWriter && !rw->wantToWrite ) {
792        rw->readCount++;
793        ret = TRUE;
794    }
795    tr_lockUnlock( &rw->lock );
796    return ret;
797
798}
799
800void
801tr_rwReaderUnlock( tr_rwlock_t * rw )
802{
803    tr_lockLock( &rw->lock );
804    --rw->readCount;
805    if( !rw->readCount )
806        tr_rwSignal( rw );
807    tr_lockUnlock( &rw->lock );
808}
809
810void
811tr_rwWriterLock( tr_rwlock_t * rw )
812{
813    tr_lockLock( &rw->lock );
814    rw->wantToWrite++;
815    while( rw->haveWriter || rw->readCount )
816        tr_condWait( &rw->writeCond, &rw->lock );
817    rw->wantToWrite--;
818    rw->haveWriter = TRUE;
819    tr_lockUnlock( &rw->lock );
820}
821
822int
823tr_rwWriterTrylock( tr_rwlock_t * rw )
824{
825    int ret = FALSE;
826    tr_lockLock( &rw->lock );
827    if( !rw->haveWriter && !rw->readCount )
828        ret = rw->haveWriter = TRUE;
829    tr_lockUnlock( &rw->lock );
830    return ret;
831}
832void
833tr_rwWriterUnlock( tr_rwlock_t * rw )
834{
835    tr_lockLock( &rw->lock );
836    rw->haveWriter = FALSE;
837    tr_rwSignal( rw );
838    tr_lockUnlock( &rw->lock );
839}
840
841void
842tr_rwClose( tr_rwlock_t * rw )
843{
844    tr_condClose( &rw->writeCond );
845    tr_condClose( &rw->readCond );
846    tr_lockClose( &rw->lock );
847}
Note: See TracBrowser for help on using the repository browser.