source: branches/daemon/daemon/torrents.c @ 1617

Last change on this file since 1617 was 1617, checked in by joshe, 15 years ago

Add a daemon frontend.

  • Property svn:keywords set to Date Rev Author Id
File size: 20.1 KB
Line 
1/******************************************************************************
2 * $Id: torrents.c 1617 2007-03-31 20:00:40Z joshe $
3 *
4 * Copyright (c) 2007 Joshua Elsasser
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#include <sys/types.h>
26#include <sys/param.h>
27#include <sys/stat.h>
28#include <sys/time.h>
29#include <sys/uio.h>
30#include <assert.h>
31#include <ctype.h>
32#include <errno.h>
33#include <event.h>
34#include <fcntl.h>
35#include <time.h>
36#include <stddef.h>
37#include <stdio.h>
38#include <stdlib.h>
39#include <string.h>
40#include <unistd.h>
41
42#include "bsdtree.h"
43#include "bencode.h"
44#include "errors.h"
45#include "misc.h"
46#include "torrents.h"
47#include "transmission.h"
48
49#define EXIT_TIMEOUT 10         /* how many seconds to wait on exit */
50#define TIMER_SECS   1          /* timer interval seconds */
51#define TIMER_USECS  0          /* timer interval microseconds */
52
53struct tor
54{
55    int             id;
56    int             deleting;
57    uint8_t         hash[SHA_DIGEST_LENGTH];
58    tr_torrent_t  * tor;
59    RB_ENTRY( tor ) idlinks;
60    RB_ENTRY( tor ) hashlinks;
61};
62
63RB_HEAD( tortree, tor );
64RB_HEAD( hashtree, tor );
65
66static struct tor * opentorrent ( const char *, const char * );
67static void         closetorrent( struct tor *, int );
68static void         starttimer  ( int );
69static void         timerfunc   ( int, short, void * );
70static int          loadstate   ( void );
71static int          savestate   ( void );
72static int          toridcmp    ( struct tor *, struct tor * );
73static int          torhashcmp  ( struct tor *, struct tor * );
74static int          writefile   ( const char *, uint8_t *, ssize_t );
75static uint8_t    * readfile    ( const char *, size_t * );
76static struct tor * idlookup    ( int, int );
77static struct tor * hashlookup  ( const uint8_t *, int );
78
79static struct event_base * gl_base      = NULL;
80static tr_handle_t       * gl_handle    = NULL;
81static struct tortree      gl_tree      = RB_INITIALIZER( &gl_tree );
82static struct hashtree     gl_hashes    = RB_INITIALIZER( &gl_hashes );
83static int                 gl_lastid    = 0;
84static struct event        gl_event;
85static time_t              gl_exiting   = 0;
86static int                 gl_exitval   = 0;
87static char                gl_state[MAXPATHLEN];
88static char                gl_newstate[MAXPATHLEN];
89
90static int                 gl_autostart = 1;
91static int                 gl_port      = TR_DEFAULT_PORT;
92static int                 gl_mapping   = 0;
93static int                 gl_uplimit   = -1;
94static int                 gl_downlimit = -1;
95static char                gl_dir[MAXPATHLEN];
96
97RB_GENERATE_STATIC( tortree, tor, idlinks, toridcmp )
98RB_GENERATE_STATIC( hashtree, tor, hashlinks, torhashcmp )
99INTCMP_FUNC( toridcmp, tor, id )
100
101void
102torrent_init( struct event_base * base )
103{
104    assert( NULL == gl_handle && NULL == gl_base );
105
106    gl_base   = base;
107    gl_handle = tr_init( "daemon" );
108
109    confpath( gl_state, sizeof gl_state, CONF_FILE_STATE, 0 );
110    strlcpy( gl_newstate, gl_state, sizeof gl_state );
111    strlcat( gl_newstate, ".new", sizeof gl_state );
112    absolutify( gl_dir, sizeof gl_dir, "." );
113
114    loadstate();
115
116    if( TR_DEFAULT_PORT != gl_port )
117    {
118        tr_setBindPort( gl_handle, gl_port );
119    }
120    tr_natTraversalEnable( gl_handle, gl_mapping );
121    tr_setGlobalUploadLimit( gl_handle, gl_uplimit );
122    tr_setGlobalDownloadLimit( gl_handle, gl_downlimit );
123}
124
125int
126torrent_add( const char * path )
127{
128    struct tor * tor;
129
130    assert( NULL != gl_handle );
131    assert( !gl_exiting );
132
133    tor = opentorrent( path, NULL );
134    if( NULL == tor )
135    {
136        return -1;
137    }
138
139    if( gl_autostart )
140    {
141        tr_torrentStart( tor->tor );
142    }
143
144    savestate();
145
146    return tor->id;
147}
148
149void
150torrent_start( int id )
151{
152    struct tor * tor;
153    tr_stat_t  * st;
154
155    assert( NULL != gl_handle );
156    assert( !gl_exiting );
157
158    tor = idlookup( id, 0 );
159    if( NULL == tor )
160    {
161        return;
162    }
163
164    st = tr_torrentStat( tor->tor );
165    if( TR_STATUS_INACTIVE & st->status )
166    {
167        tr_torrentStart( tor->tor );
168        savestate();
169    }
170}
171
172void
173torrent_stop( int id )
174{
175    struct tor * tor;
176    tr_stat_t  * st;
177
178    assert( NULL != gl_handle );
179    assert( !gl_exiting );
180
181    tor = idlookup( id, 0 );
182    if( NULL == tor )
183    {
184        return;
185    }
186
187    st = tr_torrentStat( tor->tor );
188    if( TR_STATUS_ACTIVE & st->status )
189    {
190        tr_torrentStop( tor->tor );
191        savestate();
192    }
193}
194
195void
196torrent_remove( int id )
197{
198    struct tor * tor;
199
200    assert( NULL != gl_handle );
201    assert( !gl_exiting );
202
203    tor = idlookup( id, 0 );
204    if( NULL == tor )
205    {
206        return;
207    }
208
209    closetorrent( tor, 1 );
210    savestate();
211}
212
213tr_info_t *
214torrent_info( int id )
215{
216    struct tor * tor;
217
218    assert( NULL != gl_handle );
219    assert( !gl_exiting );
220
221    tor = idlookup( id, 0 );
222    if( NULL == tor )
223    {
224        return NULL;
225    }
226
227    return tr_torrentInfo( tor->tor );
228}
229
230tr_stat_t *
231torrent_stat( int id )
232{
233    struct tor * tor;
234
235    assert( NULL != gl_handle );
236    assert( !gl_exiting );
237
238    tor = idlookup( id, 0 );
239    if( NULL == tor )
240    {
241        return NULL;
242    }
243
244    return tr_torrentStat( tor->tor );
245}
246
247int
248torrent_lookup( const uint8_t * hashstr )
249{
250    uint8_t      hash[SHA_DIGEST_LENGTH];
251    size_t       ii;
252    struct tor * tor;
253    char         buf[3];
254
255    assert( NULL != gl_handle );
256    assert( !gl_exiting );
257
258    bzero( buf, sizeof buf );
259    for( ii = 0; sizeof( hash ) > ii; ii++ )
260    {
261        if( !isxdigit( hashstr[2*ii] ) || !isxdigit( hashstr[1+2*ii] ) )
262        {
263            return -1;
264        }
265        memcpy( buf, &hashstr[2*ii], 2 );
266        hash[ii] = strtol( buf, NULL, 16 );
267    }
268
269    tor = hashlookup( hash, 0 );
270    if( NULL == tor )
271    {
272        return -1;
273    }
274
275    return tor->id;
276}
277
278void *
279torrent_iter( void * iter, int * id )
280{
281    struct tor * tor = iter;
282
283    assert( NULL != gl_handle );
284    assert( !gl_exiting );
285
286    if( NULL == tor )
287    {
288        tor = RB_MIN( tortree, &gl_tree );
289    }
290    else
291    {
292        tor = RB_NEXT( tortree, &gl_tree, tor );
293    }
294
295    while( NULL != tor && tor->deleting )
296    {
297        tor = RB_NEXT( tortree, &gl_tree, tor );
298    }
299
300    if( NULL != tor )
301    {
302        *id = tor->id;
303    }
304
305    return tor;
306}
307
308void
309torrent_exit( int exitval )
310{
311    struct tor * tor;
312
313    assert( NULL != gl_handle );
314    assert( !gl_exiting );
315    gl_exiting = time( NULL );
316    gl_exitval = exitval;
317
318    RB_FOREACH( tor, tortree, &gl_tree )
319    {
320        closetorrent( tor, 0 );
321    }
322
323    tr_natTraversalEnable( gl_handle, 0 );
324    starttimer( 1 );
325}
326
327void
328torrent_set_autostart( int autostart )
329{
330    assert( NULL != gl_handle );
331    assert( !gl_exiting );
332    gl_autostart = autostart;
333    savestate();
334}
335
336int
337torrent_get_autostart( void )
338{
339    return gl_autostart;
340}
341
342void
343torrent_set_port( int port )
344{
345    assert( NULL != gl_handle );
346    assert( !gl_exiting );
347    if( 0 < port && 0xffff > port )
348    {
349        gl_port = port;
350        tr_setBindPort( gl_handle, port );
351        savestate();
352    }
353}
354
355int
356torrent_get_port( void )
357{
358    return gl_port;
359}
360
361void
362torrent_enable_port_mapping( int automap )
363{
364    assert( NULL != gl_handle );
365    assert( !gl_exiting );
366    gl_mapping = ( automap ? 1 : 0 );
367    tr_natTraversalEnable( gl_handle, gl_mapping );
368    savestate();
369}
370
371int
372torrent_get_port_mapping( void )
373{
374    return gl_mapping;
375}
376
377void
378torrent_set_uplimit( int uplimit )
379{
380    assert( NULL != gl_handle );
381    assert( !gl_exiting );
382    gl_uplimit = uplimit;
383    tr_setGlobalUploadLimit( gl_handle, uplimit );
384    savestate();
385}
386
387int
388torrent_get_uplimit( void )
389{
390    return gl_uplimit;
391}
392
393void
394torrent_set_downlimit( int downlimit )
395{
396    assert( NULL != gl_handle );
397    assert( !gl_exiting );
398    gl_downlimit = downlimit;
399    tr_setGlobalDownloadLimit( gl_handle, downlimit );
400    savestate();
401}
402
403int
404torrent_get_downlimit( void )
405{
406    return gl_downlimit;
407}
408
409void
410torrent_set_directory( const char * path )
411{
412    assert( NULL != gl_handle );
413    assert( !gl_exiting );
414
415    absolutify( gl_dir, sizeof gl_dir, path );
416    savestate();
417}
418
419const char *
420torrent_get_directory( void )
421{
422    return gl_dir;
423}
424
425struct tor *
426opentorrent( const char * path, const char * hash )
427{
428    struct tor * tor, * found;
429    int          errcode;
430    tr_info_t  * inf;
431
432    assert( NULL == path || NULL == hash );
433    assert( NULL != path || NULL != hash );
434
435    tor = calloc( 1, sizeof *tor );
436    if( NULL == tor )
437    {
438        mallocmsg( sizeof *tor );
439        return NULL;
440    }
441
442    if( NULL != path )
443    {
444        tor->tor = tr_torrentInit( gl_handle, path, tor->hash,
445                                   TR_FLAG_SAVE, &errcode );
446    }
447    else
448    {
449        tor->tor = tr_torrentInitSaved( gl_handle, hash, 0, &errcode );
450    }
451
452    if( NULL == tor->tor )
453    {
454        found = NULL;
455        switch( errcode )
456        {
457            case TR_EINVALID:
458                errmsg( "invalid torrent file: %s", path );
459                break;
460            case TR_EUNSUPPORTED:
461                errmsg( "unsupported torrent file: %s", path );
462                break;
463            case TR_EDUPLICATE:
464                found = hashlookup( tor->hash, 1 );
465                assert( NULL != found );
466                found->deleting = 0;
467                break;
468            default:
469                errmsg( "torrent file failed to load: %s", path );
470                break;
471        }
472        free( tor );
473        return found;
474    }
475    tor->id       = ++gl_lastid;
476    tor->deleting = 0;
477
478    assert( sizeof( inf->hash ) == sizeof( tor->hash ) );
479    inf = tr_torrentInfo( tor->tor );
480    memcpy( tor->hash, inf->hash, sizeof tor->hash );
481
482    tr_torrentSetFolder( tor->tor, gl_dir );
483
484    found = RB_INSERT( tortree, &gl_tree, tor );
485    assert( NULL == found );
486    found = RB_INSERT( hashtree, &gl_hashes, tor );
487    assert( NULL == found );
488
489    return tor;
490}
491
492void
493closetorrent( struct tor * tor, int calltimer )
494{
495    tr_stat_t  * st;
496
497    if( NULL == tor || tor->deleting )
498    {
499        return;
500    }
501    tor->deleting = 1;
502
503    st = tr_torrentStat( tor->tor );
504    if( TR_STATUS_ACTIVE & st->status )
505    {
506        tr_torrentStop( tor->tor );
507    }
508
509    starttimer( calltimer );
510}
511
512void
513starttimer( int callnow )
514{
515    if( !evtimer_initialized( &gl_event ) )
516    {
517        evtimer_set( &gl_event, timerfunc, NULL );
518        event_base_set( gl_base, &gl_event );
519    }
520
521    if( callnow )
522    {
523        timerfunc( -1, EV_TIMEOUT, NULL );
524    }
525}
526
527void
528timerfunc( int fd UNUSED, short event UNUSED, void * arg UNUSED )
529{
530    struct tor         * tor, * next;
531    tr_handle_status_t * hs;
532    tr_stat_t          * st;
533    int                  stillmore;
534    struct timeval       tv;
535
536    stillmore = 0;
537    for( tor = RB_MIN( tortree, &gl_tree ); NULL != tor; tor = next )
538    {
539        next = RB_NEXT( tortree, &gl_tree, tor );
540        if( !tor->deleting )
541        {
542            continue;
543        }
544        st = tr_torrentStat( tor->tor );
545        if( TR_STATUS_PAUSE & st->status )
546        {
547            tr_torrentClose( gl_handle, tor->tor );
548            RB_REMOVE( tortree, &gl_tree, tor );
549            RB_REMOVE( hashtree, &gl_hashes, tor );
550            free( tor );
551        }
552        else
553        {
554            stillmore = 1;
555        }
556    }
557
558    if( gl_exiting )
559    {
560        if( !stillmore )
561        {
562            hs = tr_handleStatus( gl_handle );
563            if( TR_NAT_TRAVERSAL_DISABLED != hs->natTraversalStatus )
564            {
565                stillmore = 1;
566            }
567        }
568
569        if( !stillmore || EXIT_TIMEOUT <= time( NULL ) - gl_exiting )
570        {
571            if( stillmore )
572            {
573                errmsg( "timing out trackers and/or port mapping on exit" );
574            }
575            for( tor = RB_MIN( tortree, &gl_tree ); NULL != tor; tor = next )
576            {
577                next = RB_NEXT( tortree, &gl_tree, tor );
578                tr_torrentClose( gl_handle, tor->tor );
579                RB_REMOVE( tortree, &gl_tree, tor );
580                RB_REMOVE( hashtree, &gl_hashes, tor );
581                free( tor );
582            }
583            tr_close( gl_handle );
584            exit( gl_exitval );
585        }
586    }
587
588    if( stillmore )
589    {
590        bzero( &tv, sizeof tv );
591        tv.tv_sec  = TIMER_SECS;
592        tv.tv_usec = TIMER_USECS;
593        evtimer_add( &gl_event, &tv );
594    }
595}
596
597int
598loadstate( void )
599{
600    uint8_t   *  buf;
601    size_t       len;
602    benc_val_t   top, * num, * str, * list, * dict, * hash, * pause, * dir;
603    int          ii;
604    struct tor * tor;
605
606    buf = readfile( gl_state, &len );
607    if( NULL == buf )
608    {
609        return -1;
610    }
611
612    if( tr_bencLoad( buf, len, &top, NULL ) )
613    {
614        free( buf );
615        errmsg( "failed to load bencoded data from %s", gl_state );
616        return -1;
617    }
618    free( buf );
619
620    num = tr_bencDictFind( &top, "autostart" );
621    if( NULL != num && TYPE_INT == num->type )
622    {
623        gl_autostart = num->val.i;
624    }
625    num = tr_bencDictFind( &top, "port" );
626    if( NULL != num && TYPE_INT == num->type &&
627        0 < num->val.i && 0xffff > num->val.i )
628    {
629        gl_port = num->val.i;
630    }
631    num = tr_bencDictFind( &top, "port-mapping" );
632    if( NULL != num && TYPE_INT == num->type )
633    {
634        gl_mapping = num->val.i;
635    }
636    num = tr_bencDictFind( &top, "upload-limit" );
637    if( NULL != num && TYPE_INT == num->type )
638    {
639        gl_uplimit = num->val.i;
640    }
641    num = tr_bencDictFind( &top, "download-limit" );
642    if( NULL != num && TYPE_INT == num->type )
643    {
644        gl_downlimit = num->val.i;
645    }
646    str = tr_bencDictFind( &top, "default-directory" );
647    if( NULL != str && TYPE_STR == str->type )
648    {
649        strlcpy( gl_dir, str->val.s.s, sizeof gl_dir );
650    }
651
652    list = tr_bencDictFind( &top, "torrents" );
653    if( NULL != list && TYPE_LIST == list->type )
654    {
655        for( ii = 0; ii < list->val.l.count; ii++ )
656        {
657            dict = &list->val.l.vals[ii];
658            if( TYPE_DICT != dict->type )
659            {
660                continue;
661            }
662            hash = tr_bencDictFind( dict, "hash" );
663            if( NULL == hash || TYPE_STR != hash->type ||
664                2 * SHA_DIGEST_LENGTH != hash->val.s.i )
665            {
666                continue;
667            }
668            tor = opentorrent( NULL, hash->val.s.s );
669            if( NULL == tor )
670            {
671                continue;
672            }
673            dir = tr_bencDictFind( dict, "directory" );
674            if( NULL != dir && TYPE_STR == dir->type )
675            {
676                tr_torrentSetFolder( tor->tor, dir->val.s.s );
677            }
678            pause = tr_bencDictFind( dict, "paused" );
679            if( NULL != pause && TYPE_INT == pause->type && !pause->val.i )
680            {
681                tr_torrentStart( tor->tor );
682            }
683        }
684    }
685
686    return 0;
687}
688
689int
690savestate( void )
691{
692    benc_val_t   top, * list, * tor;
693    struct tor * ii;
694    tr_info_t  * inf;
695    tr_stat_t  * st;
696    uint8_t    * buf;
697    int          len;
698
699    tr_bencInit( &top, TYPE_DICT );
700    if( tr_bencDictReserve( &top, 7 ) )
701    {
702      nomem:
703        tr_bencFree( &top );
704        errmsg( "failed to save state: failed to allocate memory" );
705        return -1;
706    }
707    tr_bencInitInt( tr_bencDictAdd( &top, "autostart" ),      gl_autostart );
708    tr_bencInitInt( tr_bencDictAdd( &top, "port" ),           gl_port );
709    tr_bencInitInt( tr_bencDictAdd( &top, "port-mapping" ),   gl_mapping );
710    tr_bencInitInt( tr_bencDictAdd( &top, "upload-limit" ),   gl_uplimit );
711    tr_bencInitInt( tr_bencDictAdd( &top, "download-limit" ), gl_downlimit );
712    tr_bencInitStr( tr_bencDictAdd( &top, "default-directory" ),
713                    gl_dir, -1, 1 );
714    list = tr_bencDictAdd( &top, "torrents" );
715    tr_bencInit( list, TYPE_LIST );
716
717    len = 0;
718    RB_FOREACH( ii, tortree, &gl_tree )
719    {
720        if( !ii->deleting )
721        {
722            len++;
723        }
724    }
725    if( tr_bencListReserve( list, len ) )
726    {
727        goto nomem;
728    }
729
730    RB_FOREACH( ii, tortree, &gl_tree )
731    {
732        if( ii->deleting )
733        {
734            continue;
735        }
736        tor = tr_bencListAdd( list );
737        assert( NULL != tor );
738        tr_bencInit( tor, TYPE_DICT );
739        if( tr_bencDictReserve( tor, 3 ) )
740        {
741            goto nomem;
742        }
743        inf = tr_torrentInfo( ii->tor );
744        st  = tr_torrentStat( ii->tor );
745        tr_bencInitStr( tr_bencDictAdd( tor, "hash" ),
746                        inf->hashString, 2 * SHA_DIGEST_LENGTH, 1 );
747        tr_bencInitInt( tr_bencDictAdd( tor, "paused" ),
748                        ( TR_STATUS_INACTIVE & st->status ? 1 : 0 ) );
749        tr_bencInitStr( tr_bencDictAdd( tor, "directory" ),
750                        tr_torrentGetFolder( ii->tor ), -1, 1 );
751    }
752
753    buf = tr_bencSaveMalloc( &top, &len );
754    SAFEBENCFREE( &top );
755    if( NULL == buf )
756    {
757        errnomsg( "failed to save state: bencoding failed" );
758        return -1;
759    }
760
761    if( 0 > writefile( gl_newstate, buf, len ) )
762    {
763        free( buf );
764        return -1;
765    }
766    free( buf );
767
768    if( 0 > rename( gl_newstate, gl_state ) )
769    {
770        errnomsg( "failed to save state: failed to rename %s to %s",
771                  gl_newstate, CONF_FILE_STATE );
772        return -1;
773    }
774
775    return 0;
776}
777
778int
779torhashcmp( struct tor * left, struct tor * right )
780{
781    return memcmp( left->hash, right->hash, sizeof left->hash );
782}
783
784int
785writefile( const char * name, uint8_t * buf, ssize_t len )
786{
787    int     fd;
788    ssize_t res;
789
790    fd = open( name, O_WRONLY | O_CREAT | O_TRUNC, 0666 );
791    if( 0 > fd )
792    {
793        errnomsg( "failed to open %s for writing", name );
794        return -1;
795    }
796
797    res = write( fd, buf, len );
798    if( 0 > res )
799    {
800        errnomsg( "failed to write to %s", name );
801        return -1;
802    }
803    if( len > res )
804    {
805        errmsg( "failed to write all data to %s", name );
806        return -1;
807    }
808
809    close( fd );
810
811    return 0;
812}
813
814uint8_t *
815readfile( const char * name, size_t * len )
816{
817    struct stat sb;
818    int         fd;
819    uint8_t   * buf;
820    ssize_t     res;
821
822    fd = open( name, O_RDONLY );
823    if( 0 > fd )
824    {
825        if( ENOENT != errno )
826        {
827            errnomsg( "failed to open %s for reading", name );
828        }
829        return NULL;
830    }
831
832    if( 0 > fstat( fd, &sb ) )
833    {
834        errnomsg( "failed to stat %s", name );
835        return NULL;
836    }
837
838    buf = malloc( sb.st_size );
839    if( NULL == buf )
840    {
841        mallocmsg( sb.st_size );
842        return NULL;
843    }
844
845    res = read( fd, buf, sb.st_size );
846    if( 0 > res )
847    {
848        errnomsg( "failed to read from %s", name );
849        free( buf );
850        return NULL;
851    }
852    if( res < sb.st_size )
853    {
854        errmsg( "failed to read all data from %s", name );
855        free( buf );
856        return NULL;
857    }
858
859    close( fd );
860    *len = res;
861
862    return buf;
863}
864
865struct tor *
866idlookup( int id, int wantdel )
867{
868    struct tor key, * found;
869
870    bzero( &key, sizeof key );
871    key.id = id;
872    found = RB_FIND( tortree, &gl_tree, &key );
873    if( NULL != found && !wantdel && found->deleting )
874    {
875        found = NULL;
876    }
877
878    return found;
879}
880
881struct tor *
882hashlookup( const uint8_t * hash, int wantdel )
883{
884    struct tor key, * found;
885
886    bzero( &key, sizeof key );
887    memcpy( key.hash, hash, sizeof key.hash );
888    found = RB_FIND( hashtree, &gl_hashes, &key );
889    if( NULL != found && !wantdel && found->deleting )
890    {
891        found = NULL;
892    }
893
894    return found;
895}
Note: See TracBrowser for help on using the repository browser.