/****************************************************************************** * $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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; }