transmission/daemon/client.c

1076 lines
25 KiB
C

/******************************************************************************
* $Id$
*
* Copyright (c) 2007 Joshua Elsasser
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/un.h>
#include <assert.h>
#include <errno.h>
#include <event.h>
#include <limits.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libtransmission/bsdtree.h>
#include <libtransmission/bencode.h>
#include <libtransmission/ipcparse.h>
#include <libtransmission/trcompat.h>
#include "bsdqueue.h"
#include "client.h"
#include "errors.h"
#include "misc.h"
/* time out server after this many seconds */
#define SERVER_TIMEOUT ( 15 )
struct con
{
int infd;
int outfd;
struct ipc_info * ipc;
struct bufferevent * evin;
struct bufferevent * evout;
};
struct req
{
enum ipc_msg id;
int64_t tag;
struct strlist * strs;
int64_t num;
char * str;
size_t listlen;
int64_t * numlist;
uint8_t * buf;
int types;
SLIST_ENTRY( req ) next;
};
SLIST_HEAD( reqlist, req );
struct resp
{
int64_t tag;
cl_infofunc infocb;
cl_statfunc statcb;
RB_ENTRY( resp ) links;
};
RB_HEAD( resptree, resp );
static struct req * addreq ( enum ipc_msg, int64_t, struct resp ** );
static int addintlistreq ( enum ipc_msg, size_t, const int * );
static void noop ( struct bufferevent *, void * );
static void noway ( struct bufferevent *, void * );
static void didwrite ( struct bufferevent *, void * );
static void ohshit ( struct bufferevent *, short, void * );
static void canread ( struct bufferevent *, void * );
static void flushreqs ( struct con * );
static int sendvers ( struct con * );
static void infomsg ( enum ipc_msg, benc_val_t *, int64_t, void * );
static void statmsg ( enum ipc_msg, benc_val_t *, int64_t, void * );
static void defmsg ( enum ipc_msg, benc_val_t *, int64_t, void * );
static void cbdone ( struct resp * );
static int64_t getinfoint ( enum ipc_msg, benc_val_t *, int, int64_t );
static char * getinfostr ( enum ipc_msg, benc_val_t *, int, char * );
static int resptagcmp ( struct resp *, struct resp * );
RB_GENERATE_STATIC( resptree, resp, links, resptagcmp )
INTCMP_FUNC( resptagcmp, resp, tag )
static struct event_base * gl_base = NULL;
static struct ipc_funcs * gl_tree = NULL;
static struct reqlist gl_reqs = SLIST_HEAD_INITIALIZER( &gl_reqs );
static struct resptree gl_resps = RB_INITIALIZER( &gl_resps );
static int64_t gl_tag = 0;
static int gl_proxy = -1;
int
client_init( struct event_base * base )
{
assert( NULL == gl_base && NULL == gl_tree );
gl_base = base;
gl_tree = ipc_initmsgs();
if( NULL == gl_tree )
{
return -1;
}
if( 0 > ipc_addmsg( gl_tree, IPC_MSG_INFO, infomsg ) ||
0 > ipc_addmsg( gl_tree, IPC_MSG_STAT, statmsg ) )
{
return -1;
}
ipc_setdefmsg( gl_tree, defmsg );
return 0;
}
int
client_new_sock( const char * path )
{
struct sockaddr_un sa;
int fd;
struct con * con;
assert( NULL != gl_base );
assert( 0 > gl_proxy );
assert( NULL != path );
gl_proxy = 0;
memset( &sa, 0, sizeof sa );
sa.sun_family = AF_LOCAL;
strlcpy( sa.sun_path, path, sizeof sa.sun_path );
fd = socket( AF_UNIX, SOCK_STREAM, 0 );
if( 0 > fd )
{
errnomsg( "failed to create socket" );
return -1;
}
if( 0 > connect( fd, ( struct sockaddr * )&sa, SUN_LEN( &sa ) ) )
{
errnomsg( "failed to connect to socket file: %s", path );
close( fd );
return -1;
}
con = calloc( 1, sizeof *con );
if( NULL == con )
{
mallocmsg( sizeof *con );
close( fd );
return -1;
}
con->ipc = ipc_newcon( gl_tree );
if( NULL == con->ipc )
{
mallocmsg( sizeof *con->ipc );
close( fd );
free( con );
}
con->infd = fd;
con->evin = bufferevent_new( fd, canread, didwrite, ohshit, con );
if( NULL == con->evin )
{
errnomsg( "failed to create bufferevent" );
close( fd );
ipc_freecon( con->ipc );
free( con );
return -1;
}
con->outfd = con->infd;
con->evout = con->evin;
/* XXX bufferevent_base_set( gl_base, con->evin ); */
bufferevent_settimeout( con->evin, SERVER_TIMEOUT, SERVER_TIMEOUT );
bufferevent_enable( con->evin, EV_READ );
if( 0 > sendvers( con ) )
{
exit( 1 );
}
return 0;
}
int
client_new_cmd( char * const * cmd )
{
struct con * con;
int tocmd[2], fromcmd[2];
pid_t kid;
assert( NULL != gl_base );
assert( 0 > gl_proxy );
assert( NULL != cmd && NULL != cmd[0] );
gl_proxy = 1;
if( 0 > pipe( tocmd ) )
{
errnomsg( "failed to create pipe" );
return -1;
}
if( 0 > pipe( fromcmd ) )
{
errnomsg( "failed to create pipe" );
close( tocmd[0] );
close( tocmd[1] );
return -1;
}
kid = fork();
if( 0 > kid )
{
close( tocmd[0] );
close( tocmd[1] );
close( fromcmd[0] );
close( fromcmd[1] );
return -1;
}
else if( 0 == kid )
{
if( 0 > dup2( tocmd[0], STDIN_FILENO ) ||
0 > dup2( fromcmd[1], STDOUT_FILENO ) )
{
errnomsg( "failed to duplicate descriptors" );
_exit( 1 );
}
close( tocmd[0] );
close( tocmd[1] );
close( fromcmd[0] );
close( fromcmd[1] );
execvp( cmd[0], cmd );
errnomsg( "failed to execute: %s", cmd[0] );
_exit( 1 );
}
close( tocmd[0] );
close( fromcmd[1] );
con = calloc( 1, sizeof *con );
if( NULL == con )
{
mallocmsg( sizeof *con );
close( tocmd[1] );
close( fromcmd[0] );
return -1;
}
con->infd = fromcmd[0];
con->evin = bufferevent_new( con->infd, canread, noop, ohshit, con );
if( NULL == con->evin )
{
free( con );
close( tocmd[1] );
close( fromcmd[0] );
return -1;
}
/* XXX bufferevent_base_set( gl_base, con->evin ); */
bufferevent_settimeout( con->evin, SERVER_TIMEOUT, SERVER_TIMEOUT );
bufferevent_enable( con->evin, EV_READ );
con->outfd = tocmd[1];
con->evout = bufferevent_new( con->outfd, noway, didwrite, ohshit, con );
if( NULL == con->evout )
{
bufferevent_free( con->evin );
bufferevent_free( con->evout );
free( con );
close( tocmd[1] );
close( fromcmd[0] );
return -1;
}
/* XXX bufferevent_base_set( gl_base, con->evout ); */
bufferevent_settimeout( con->evout, SERVER_TIMEOUT, SERVER_TIMEOUT );
bufferevent_enable( con->evout, EV_READ );
con->ipc = ipc_newcon( gl_tree );
if( NULL == con->ipc )
{
mallocmsg( sizeof *con->ipc );
bufferevent_free( con->evin );
bufferevent_free( con->evout );
free( con );
close( tocmd[1] );
close( fromcmd[0] );
return -1;
}
if( 0 > sendvers( con ) )
{
exit( 1 );
}
return 0;
}
struct req *
addreq( enum ipc_msg id, int64_t tag, struct resp ** resp )
{
struct req * req;
assert( ( 0 < tag && NULL != resp ) || ( 0 >= tag && NULL == resp ) );
req = calloc( 1, sizeof *req );
if( NULL == req )
{
mallocmsg( sizeof *req );
return NULL;
}
if( NULL != resp )
{
*resp = calloc( 1, sizeof **resp );
if( NULL == *resp )
{
mallocmsg( sizeof **resp );
free( req );
return NULL;
}
(*resp)->tag = tag;
RB_INSERT( resptree, &gl_resps, *resp );
}
req->id = id;
req->tag = tag;
SLIST_INSERT_HEAD( &gl_reqs, req, next );
return req;
}
int
client_quit( void )
{
return ( NULL == addreq( IPC_MSG_QUIT, -1, NULL ) ? -1 : 0 );
}
int
client_addfiles( struct strlist * list )
{
struct stritem * ii;
uint8_t * buf;
size_t size;
struct req * req;
if( gl_proxy )
{
SLIST_FOREACH( ii, list, next )
{
buf = readfile( ii->str, &size );
req = addreq( IPC_MSG_ADDONEFILE, -1, NULL );
if( NULL == req )
{
free( buf );
return -1;
}
req->buf = buf;
req->listlen = size;
}
}
else
{
req = addreq( IPC_MSG_ADDMANYFILES, -1, NULL );
if( NULL == req )
{
return -1;
}
/* XXX need to move arg parsing back here or something */
req->strs = list;
}
return 0;
}
int
client_automap( int automap )
{
struct req * req;
req = addreq( IPC_MSG_AUTOMAP, -1, NULL );
if( NULL == req )
{
return -1;
}
req->num = ( automap ? 1 : 0 );
return 0;
}
int
client_pex( int pex )
{
struct req * req;
req = addreq( IPC_MSG_PEX, -1, NULL );
if( NULL == req )
{
return -1;
}
req->num = ( pex ? 1 : 0 );
return 0;
}
int
client_port( int port )
{
struct req * req;
req = addreq( IPC_MSG_PORT, -1, NULL );
if( NULL == req )
{
return -1;
}
req->num = port;
return 0;
}
int
client_downlimit( int limit )
{
struct req * req;
req = addreq( IPC_MSG_DOWNLIMIT, -1, NULL );
if( NULL == req )
{
return -1;
}
req->num = ( 0 > limit ? -1 : limit );
return 0;
}
int
client_uplimit( int limit )
{
struct req * req;
req = addreq( IPC_MSG_UPLIMIT, -1, NULL );
if( NULL == req )
{
return -1;
}
req->num = ( 0 > limit ? -1 : limit );
return 0;
}
int
client_dir( const char * dir )
{
struct req * req;
char * dircpy;
dircpy = strdup( dir );
if( NULL == dircpy )
{
mallocmsg( strlen( dir ) );
return -1;
}
req = addreq( IPC_MSG_DIR, -1, NULL );
if( NULL == req )
{
free( dircpy );
return -1;
}
req->str = dircpy;
return 0;
}
int
addintlistreq( enum ipc_msg which, size_t len, const int * list )
{
struct req * req;
int64_t * duplist;
size_t ii;
assert( ( 0 == len && NULL == list ) || ( 0 < len && NULL != list ) );
duplist = NULL;
if( NULL != list )
{
duplist = calloc( len, sizeof duplist[0] );
if( NULL == duplist )
{
mallocmsg( len * sizeof( duplist[0] ) );
return -1;
}
}
req = addreq( which, -1, NULL );
if( NULL == req )
{
free( duplist );
return -1;
}
for( ii = 0; len > ii; ii++ )
{
duplist[ii] = list[ii];
}
req->listlen = len;
req->numlist = duplist;
return 0;
}
int
client_start( size_t len, const int * list )
{
enum ipc_msg id;
id = ( NULL == list ? IPC_MSG_STARTALL : IPC_MSG_START );
return addintlistreq( id, len, list );
}
int
client_stop( size_t len, const int * list )
{
enum ipc_msg id;
id = ( NULL == list ? IPC_MSG_STOPALL : IPC_MSG_STOP );
return addintlistreq( id, len, list );
}
int
client_remove( size_t len, const int * list )
{
enum ipc_msg id;
id = ( NULL == list ? IPC_MSG_REMOVEALL : IPC_MSG_REMOVE );
return addintlistreq( id, len, list );
}
int
client_list( cl_infofunc func )
{
struct req * req;
struct resp * resp;
req = addreq( IPC_MSG_GETINFOALL, ++gl_tag, &resp );
if( NULL == req )
{
return -1;
}
resp->infocb = func;
req->types = IPC_INF_NAME | IPC_INF_HASH;
return 0;
}
int
client_info( cl_infofunc func )
{
struct req * req;
struct resp * resp;
req = addreq( IPC_MSG_GETINFOALL, ++gl_tag, &resp );
if( NULL == req )
{
return -1;
}
resp->infocb = func;
req->types = IPC_INF_NAME | IPC_INF_HASH | IPC_INF_SIZE;
return 0;
}
int
client_hashids( cl_infofunc func )
{
struct req * req;
struct resp * resp;
req = addreq( IPC_MSG_GETINFOALL, ++gl_tag, &resp );
if( NULL == req )
{
return -1;
}
resp->infocb = func;
req->types = IPC_INF_HASH;
return 0;
}
int
client_status( cl_statfunc func )
{
struct req * req;
struct resp * resp;
req = addreq( IPC_MSG_GETSTATALL, ++gl_tag, &resp );
if( NULL == req )
{
return -1;
}
resp->statcb = func;
req->types = IPC_ST_STATE | IPC_ST_ETA | IPC_ST_COMPLETED |
IPC_ST_DOWNSPEED | IPC_ST_UPSPEED | IPC_ST_DOWNTOTAL | IPC_ST_UPTOTAL |
IPC_ST_ERROR | IPC_ST_ERRMSG;
return 0;
}
void
noop( struct bufferevent * ev UNUSED, void * arg UNUSED )
{
/* libevent prior to 1.2 couldn't handle a NULL write callback */
}
void
noway( struct bufferevent * evin, void * arg UNUSED )
{
/* this shouldn't happen, but let's drain the buffer anyway */
evbuffer_drain( EVBUFFER_INPUT( evin ),
EVBUFFER_LENGTH( EVBUFFER_INPUT( evin ) ) );
}
void
didwrite( struct bufferevent * evout, void * arg )
{
struct con * con = arg;
assert( evout == con->evout );
flushreqs( con );
}
void
ohshit( struct bufferevent * ev UNUSED, short what, void * arg UNUSED )
{
if( EVBUFFER_EOF & what )
{
errmsg( "server closed connection" );
}
else if( EVBUFFER_TIMEOUT & what )
{
errmsg( "server connection timed out" );
}
else if( EVBUFFER_READ & what )
{
errmsg( "read error on server connection" );
}
else if( EVBUFFER_WRITE & what )
{
errmsg( "write error on server connection" );
}
else if( EVBUFFER_ERROR & what )
{
errmsg( "error on server connection" );
}
else
{
errmsg( "unknown error on server connection: 0x%x", what );
}
exit( 1 );
}
void
canread( struct bufferevent * evin, void * arg )
{
struct con * con = arg;
uint8_t * buf;
size_t len;
ssize_t res;
assert( evin == con->evin );
buf = EVBUFFER_DATA( EVBUFFER_INPUT( evin ) );
len = EVBUFFER_LENGTH( EVBUFFER_INPUT( evin ) );
if( IPC_MIN_MSG_LEN > len )
{
return;
}
res = ipc_parse( con->ipc, buf, len, con );
if( 0 > res )
{
switch( errno )
{
case EPERM:
errmsg( "unsupported protocol version" );
break;
case EINVAL:
errmsg( "protocol parse error" );
break;
default:
errnomsg( "parsing failed" );
break;
}
exit( 1 );
}
if( 0 < res )
{
evbuffer_drain( EVBUFFER_INPUT( evin ), res );
flushreqs( con );
}
}
void
flushreqs( struct con * con )
{
struct req * req;
uint8_t * buf;
size_t buflen, ii;
benc_val_t pk, * val;
struct stritem * jj;
if( !HASVERS( con->ipc ) )
{
return;
}
if( SLIST_EMPTY( &gl_reqs ) && RB_EMPTY( &gl_resps ) )
{
exit( 0 );
}
while( !SLIST_EMPTY( &gl_reqs ) )
{
req = SLIST_FIRST( &gl_reqs );
SLIST_REMOVE_HEAD( &gl_reqs, next );
buf = NULL;
switch( req->id )
{
case IPC_MSG_QUIT:
case IPC_MSG_STARTALL:
case IPC_MSG_STOPALL:
case IPC_MSG_REMOVEALL:
buf = ipc_mkempty( con->ipc, &buflen, req->id, req->tag );
break;
case IPC_MSG_ADDMANYFILES:
ii = 0;
SLIST_FOREACH( jj, req->strs, next )
{
ii++;
}
val = ipc_initval( con->ipc, req->id, -1, &pk, TYPE_LIST );
if( NULL != val && !tr_bencListReserve( val, ii ) )
{
SLIST_FOREACH( jj, req->strs, next )
{
tr_bencInitStr( tr_bencListAdd( val ),
jj->str, -1, 1 );
}
buf = ipc_mkval( &pk, &buflen );
SAFEBENCFREE( &pk );
}
SAFEFREESTRLIST( req->strs );
break;
case IPC_MSG_ADDONEFILE:
val = ipc_initval( con->ipc, req->id, -1, &pk, TYPE_DICT );
if( NULL != val && !tr_bencDictReserve( val, 1 ) )
{
tr_bencInitStr( tr_bencDictAdd( val, "data" ),
req->buf, req->listlen, 1 );
buf = ipc_mkval( &pk, &buflen );
SAFEBENCFREE( &pk );
}
SAFEFREE( req->buf );
break;
case IPC_MSG_AUTOMAP:
case IPC_MSG_PORT:
case IPC_MSG_DOWNLIMIT:
case IPC_MSG_UPLIMIT:
case IPC_MSG_PEX:
buf = ipc_mkint( con->ipc, &buflen, req->id, -1, req->num );
break;
case IPC_MSG_DIR:
buf = ipc_mkstr( con->ipc, &buflen, req->id, -1, req->str );
SAFEFREE( req->str );
break;
case IPC_MSG_START:
case IPC_MSG_STOP:
case IPC_MSG_REMOVE:
val = ipc_initval( con->ipc, req->id, -1, &pk, TYPE_LIST );
if( NULL != val && !tr_bencListReserve( val, req->listlen ) )
{
for( ii = 0; ii < req->listlen; ii++ )
{
tr_bencInitInt( tr_bencListAdd( val ),
req->numlist[ii] );
}
buf = ipc_mkval( &pk, &buflen );
SAFEBENCFREE( &pk );
}
SAFEFREE( req->numlist );
break;
case IPC_MSG_GETINFOALL:
case IPC_MSG_GETSTATALL:
buf = ipc_mkgetinfo( con->ipc, &buflen, req->id, req->tag,
req->types, NULL );
break;
default:
assert( 0 );
return;
}
SAFEFREE( req );
if( NULL == buf )
{
if( EPERM == errno )
{
errmsg( "message not supported by server" );
}
else
{
errnomsg( "failed to create message" );
}
exit( 1 );
}
if( 0 > bufferevent_write( con->evout, buf, buflen ) )
{
errmsg( "failed to buffer %zd bytes of data for write", buflen );
exit( 1 );
}
free( buf );
}
}
int
sendvers( struct con * con )
{
uint8_t * buf;
size_t len;
buf = ipc_mkvers( &len, "Transmission remote" LONG_VERSION_STRING );
if( NULL == buf )
{
if( EPERM == errno )
{
errmsg( "message not supported by server" );
}
else
{
errnomsg( "failed to create message" );
}
return -1;
}
if( 0 > bufferevent_write( con->evout, buf, len ) )
{
free( buf );
errmsg( "failed to buffer %i bytes of data for write", ( int )len );
return -1;
}
free( buf );
return 0;
}
void
infomsg( enum ipc_msg msgid, benc_val_t * list, int64_t tag,
void * arg UNUSED )
{
benc_val_t * dict;
int ii;
struct cl_info inf;
int64_t id;
struct resp * resp, key;
assert( IPC_MSG_INFO == msgid );
if( TYPE_LIST != list->type )
{
return;
}
memset( &key, 0, sizeof key );
key.tag = tag;
resp = RB_FIND( resptree, &gl_resps, &key );
if( NULL == resp || NULL == resp->infocb )
{
return;
}
RB_REMOVE( resptree, &gl_resps, resp );
for( ii = 0; list->val.l.count > ii; ii++ )
{
dict = &list->val.l.vals[ii];
if( TYPE_DICT != dict->type )
{
continue;
}
id = getinfoint( msgid, dict, IPC_INF_ID, -1 );
inf.name = getinfostr( msgid, dict, IPC_INF_NAME, NULL );
inf.hash = getinfostr( msgid, dict, IPC_INF_HASH, NULL );
inf.size = getinfoint( msgid, dict, IPC_INF_SIZE, -1 );
if( !TORRENT_ID_VALID( id ) )
{
continue;
}
inf.id = id;
resp->infocb( &inf );
}
cbdone( resp );
free( resp );
}
void
statmsg( enum ipc_msg msgid, benc_val_t * list, int64_t tag,
void * arg UNUSED )
{
benc_val_t * dict;
int ii;
int64_t id;
struct cl_stat st;
struct resp * resp, key;
assert( IPC_MSG_STAT == msgid );
if( TYPE_LIST != list->type )
{
return;
}
memset( &key, 0, sizeof key );
key.tag = tag;
resp = RB_FIND( resptree, &gl_resps, &key );
if( NULL == resp || NULL == resp->statcb )
{
return;
}
RB_REMOVE( resptree, &gl_resps, resp );
for( ii = 0; list->val.l.count > ii; ii++ )
{
dict = &list->val.l.vals[ii];
if( TYPE_DICT != dict->type )
{
continue;
}
id = getinfoint( msgid, dict, IPC_ST_ID, -1 );
st.state = getinfostr( msgid, dict, IPC_ST_STATE, NULL );
st.eta = getinfoint( msgid, dict, IPC_ST_ETA, -1 );
st.done = getinfoint( msgid, dict, IPC_ST_COMPLETED, -1 );
st.ratedown = getinfoint( msgid, dict, IPC_ST_DOWNSPEED, -1 );
st.rateup = getinfoint( msgid, dict, IPC_ST_UPSPEED, -1 );
st.totaldown = getinfoint( msgid, dict, IPC_ST_DOWNTOTAL, -1 );
st.totalup = getinfoint( msgid, dict, IPC_ST_UPTOTAL, -1 );
st.error = getinfostr( msgid, dict, IPC_ST_ERROR, NULL );
st.errmsg = getinfostr( msgid, dict, IPC_ST_ERRMSG, NULL );
if( !TORRENT_ID_VALID( id ) )
{
continue;
}
st.id = id;
resp->statcb( &st );
}
cbdone( resp );
free( resp );
}
void
defmsg( enum ipc_msg msgid, benc_val_t * val, int64_t tag, void * arg UNUSED )
{
struct resp * resp, key;
switch( msgid )
{
case IPC_MSG_FAIL:
if( TYPE_STR == val->type && NULL != val->val.s.s )
{
errmsg( "request failed: %s", val->val.s.s );
}
else
{
errmsg( "request failed" );
}
break;
case IPC_MSG_NOTSUP:
errmsg( "request message not supported" );
break;
default:
break;
}
memset( &key, 0, sizeof key );
key.tag = tag;
resp = RB_FIND( resptree, &gl_resps, &key );
if( NULL == resp )
{
return;
}
RB_REMOVE( resptree, &gl_resps, resp );
cbdone( resp );
free( resp );
}
void
cbdone( struct resp * resp )
{
if( NULL != resp->infocb )
{
resp->infocb( NULL );
}
else if( NULL != resp->statcb )
{
resp->statcb( NULL );
}
}
int64_t
getinfoint( enum ipc_msg msgid, benc_val_t * dict, int type, int64_t defval )
{
benc_val_t * val;
val = tr_bencDictFind( dict, ipc_infoname( msgid, type ) );
if( NULL != val && TYPE_INT == val->type )
{
return val->val.i;
}
return defval;
}
char *
getinfostr( enum ipc_msg msgid, benc_val_t * dict, int type, char * defval )
{
benc_val_t * val;
val = tr_bencDictFind( dict, ipc_infoname( msgid, type ) );
if( NULL != val && TYPE_STR == val->type )
{
return val->val.s.s ;
}
return defval;
}