Merge daemon branch to trunk.

This commit is contained in:
Josh Elsasser
2007-04-18 16:39:10 +00:00
parent a2645ad7cd
commit 132e4f5c8c
32 changed files with 7851 additions and 8 deletions

4
.gitignore vendored
View File

@@ -6,6 +6,10 @@ beos/Transmission
/build
cli/.depend
cli/transmissioncli
daemon/.depend
daemon/transmission-daemon
daemon/transmission-proxy
daemon/transmission-remote
gtk/.depend
gtk/defines.h
gtk/po/*.mo

View File

@@ -10,6 +10,7 @@ Eric Petit <titer@m0k.org>
Josh Elsasser <josh@elsasser.org>
+ Back-end
+ GTK+ interface
+ Daemon interface
Mitchell Livingston <livings124@gmail.com>
+ OS X interface

View File

@@ -92,6 +92,9 @@ program was written by
and
.An Mitchell Livingston Aq livings124@gmail.com .
.Sh SEE ALSO
.Xr transmission-gtk 1
.Xr transmission-daemon 1 ,
.Xr transmission-gtk 1 ,
.Xr transmission-proxy 1 ,
.Xr transmission-remote 1
.Pp
http://transmission.m0k.org

107
configure vendored
View File

@@ -12,6 +12,7 @@ MATH=no
PTHREAD=no
OPENSSL=
GTK=
LIBEVENT=
PREFIX=/usr/local
CC="${CC-cc}"
CFLAGS="${CFLAGS}"
@@ -30,6 +31,7 @@ usage()
Options:
--disable-openssl Disable OpenSSL, use built-in SHA1 implementation
--disable-gtk Don't build the GTK+ GUI
--disable-daemon Don't build the daemon
--prefix=PATH Installation path
--verbose Display additional information for debugging
@@ -138,6 +140,46 @@ EOF
rm -f testconf*
}
strlcpy_test()
{
verbose strlcpy_test
cat > testconf.c << EOF
#include <string.h>
int main()
{
char buf[] = "1234:p";
strlcpy( buf, "foo", sizeof buf );
return ( 0 == memcmp( buf, "foo\0:p", 6 ) ? 0 : 1 );
}
EOF
if runcmd $CC -o testconf testconf.c && runcmd ./testconf
then
CFLAGS="$CFLAGS -DHAVE_STRLCPY"
fi
rm -f testconf*
}
strlcat_test()
{
verbose strlcat_test
cat > testconf.c << EOF
#include <string.h>
int main()
{
char buf[] = "1234567:p";
buf[0] = '\0';
strlcat( buf, "foo", sizeof buf );
strlcat( buf, "bar", sizeof buf );
return ( 0 == memcmp( buf, "foobar\0:p", 6 ) ? 0 : 1 );
}
EOF
if runcmd $CC -o testconf testconf.c && runcmd ./testconf
then
CFLAGS="$CFLAGS -DHAVE_STRLCAT"
fi
rm -f testconf*
}
gettext_test()
{
verbose gettext_test
@@ -212,6 +254,45 @@ EOF
fi
}
libevent_test()
{
verbose libevent_test
cat > testconf.c <<EOF
#include <sys/types.h>
#include <sys/time.h>
#include <event.h>
int main()
{
event_init();
return 0;
}
EOF
if runcmd $CC $CFLAGS -levent $LDFLAGS -o testconf testconf.c
then
LIBEVENT=yes
LDFLAGS_EVENT="-levent"
rm -f testconf*
return 0
fi
for event_testdir in $PREFIX /usr/local /usr/X11R6 /usr/pkg
do
if runcmd $CC $CFLAGS -I$event_testdir/include $LDFLAGS -levent \
-L$event_testdir/lib -o testconf testconf.c
then
LIBEVENT=yes
CFLAGS_EVENT="-I$event_testdir/include"
LDFLAGS_EVENT="-levent -L$event_testdir/lib"
rm -f testconf*
return 0
fi
done
LIBEVENT=no
rm -f testconf*
return 1
}
#
# Parse options
#
@@ -222,6 +303,9 @@ while [ $# -ne 0 ]; do
x--disable-openssl|x--without-openssl)
OPENSSL=no
;;
x--disable-daemon|x--without-daemon)
LIBEVENT=no
;;
x--disable-gtk|x--without-gtk)
GTK=no
;;
@@ -341,12 +425,32 @@ EOF
fi
fi
#
# libevent settings
#
echo -n "Daemon: "
if [ "$LIBEVENT" = no ]; then
echo "disabled"
else
if libevent_test; then
echo "yes"
else
echo "no (can't find libevent)"
fi
fi
#
# Math functions
#
lm_test
lrintf_test
#
# String functions
#
strlcpy_test
strlcat_test
#
# Generate config.mk
#
@@ -359,6 +463,7 @@ BEOS_NETSERVER = $BEOS_NETSERVER
PTHREAD = $PTHREAD
OPENSSL = $OPENSSL
GTK = $GTK
DAEMON = $LIBEVENT
CC = $CC
CFLAGS = $CFLAGS
CXX = $CXX
@@ -366,6 +471,8 @@ CXXFLAGS = $CXXFLAGS
LDFLAGS = $LDFLAGS
CFLAGS_GTK = $CFLAGS_GTK
LDFLAGS_GTK = $LDFLAGS_GTK
CFLAGS_EVENT = $CFLAGS_EVENT
LDFLAGS_EVENT = $LDFLAGS_EVENT
EOF
echo

1055
daemon/client.c Normal file

File diff suppressed because it is too large Load Diff

75
daemon/client.h Normal file
View File

@@ -0,0 +1,75 @@
/******************************************************************************
* $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.
*****************************************************************************/
#ifndef TR_DAEMON_CLIENT_H
#define TR_DAEMON_CLIENT_H
struct event_base;
struct strlist;
struct cl_info
{
int id;
const char * name;
const char * hash;
int64_t size;
};
struct cl_stat
{
int id;
const char * state;
int64_t eta;
int64_t done;
int64_t ratedown;
int64_t rateup;
int64_t totaldown;
int64_t totalup;
const char * error;
const char * errmsg;
};
typedef void ( * cl_infofunc )( const struct cl_info * );
typedef void ( * cl_statfunc )( const struct cl_stat * );
int client_init ( struct event_base * );
int client_new_sock ( const char * );
int client_new_cmd ( char * const * );
int client_quit ( void );
int client_addfiles ( struct strlist * );
int client_port ( int );
int client_automap ( int );
int client_pex ( int );
int client_downlimit( int );
int client_uplimit ( int );
int client_dir ( const char * );
int client_start ( size_t, const int * );
int client_stop ( size_t, const int * );
int client_remove ( size_t, const int * );
int client_list ( cl_infofunc );
int client_info ( cl_infofunc );
int client_hashids ( cl_infofunc );
int client_status ( cl_statfunc );
#endif /* TR_DAEMON_CLIENT_H */

323
daemon/daemon.c Normal file
View File

@@ -0,0 +1,323 @@
/******************************************************************************
* $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/param.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/uio.h>
#include <sys/un.h>
#include <assert.h>
#include <errno.h>
#include <event.h>
#include <fcntl.h>
#include <getopt.h>
#include <signal.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "errors.h"
#include "misc.h"
#include "server.h"
#include "torrents.h"
#include "trcompat.h"
#include "version.h"
static void usage ( const char *, ... );
static int readargs ( int, char ** );
static int trylocksock ( void );
static int getlock ( const char * );
static int getsock ( const char * );
static void exitcleanup ( void );
static void setupsigs ( struct event_base * );
static void gotsig ( int, short, void * );
static int gl_lockfd = -1;
static char gl_lockpath[MAXPATHLEN] = "";
static int gl_sockfd = -1;
static char gl_sockpath[MAXPATHLEN] = "";
int
main( int argc, char ** argv )
{
struct event_base * evbase;
int nofork, sockfd;
setmyname( argv[0] );
nofork = readargs( argc, argv );
if( !nofork )
{
if( 0 > daemon( 1, 0 ) )
{
errnomsg( "failed to daemonize" );
exit( 1 );
}
errsyslog( 1 );
}
atexit( exitcleanup );
sockfd = trylocksock();
if( 0 > sockfd )
{
exit( 1 );
}
evbase = event_init();
setupsigs( evbase );
torrent_init( evbase );
server_init( evbase );
server_listen( sockfd );
event_dispatch();
/* XXX event_base_dispatch( evbase ); */
return 1;
}
void
usage( const char * msg, ... )
{
va_list ap;
if( NULL != msg )
{
printf( "%s: ", getmyname() );
va_start( ap, msg );
vprintf( msg, ap );
va_end( ap );
printf( "\n" );
}
printf(
"usage: %s [-fh]\n"
"\n"
"Transmission %s (r%d) http://transmission.m0k.org/\n"
"A free, lightweight BitTorrent client with a simple, intuitive interface\n"
"\n"
" -f --foreground Run in the foreground and log to stderr\n"
" -h --help Display this message and exit\n"
"\n"
"To add torrents or set options, use the transmission-remote program.\n",
getmyname(), VERSION_STRING, VERSION_REVISION );
exit( 0 );
}
int
readargs( int argc, char ** argv )
{
char optstr[] = "fh";
struct option longopts[] =
{
{ "foreground", no_argument, NULL, 'f' },
{ "help", no_argument, NULL, 'h' },
{ NULL, 0, NULL, 0 }
};
int opt, nofork;
nofork = 0;
while( 0 <= ( opt = getopt_long( argc, argv, optstr, longopts, NULL ) ) )
{
switch( opt )
{
case 'f':
nofork = 1;
break;
default:
usage( NULL );
break;
}
}
return nofork;
}
int
trylocksock( void )
{
char path[MAXPATHLEN];
int fd;
confpath( path, sizeof path, NULL, CONF_PATH_TYPE_DAEMON );
if( 0 > mkdir( path, 0777 ) && EEXIST != errno )
{
errnomsg( "failed to create directory: %s", path );
return -1;
}
confpath( path, sizeof path, CONF_FILE_LOCK, 0 );
fd = getlock( path );
if( 0 > fd )
{
return -1;
}
gl_lockfd = fd;
strlcpy( gl_lockpath, path, sizeof gl_lockpath );
confpath( path, sizeof path, CONF_FILE_SOCKET, 0 );
fd = getsock( path );
if( 0 > fd )
{
return -1;
}
gl_sockfd = fd;
strlcpy( gl_sockpath, path, sizeof gl_sockpath );
return fd;
}
int
getlock( const char * path )
{
struct flock lk;
int fd;
char pid[64];
fd = open( path, O_RDWR | O_CREAT, 0666 );
if( 0 > fd )
{
errnomsg( "failed to open file: %s", path );
return -1;
}
bzero( &lk, sizeof lk );
lk.l_start = 0;
lk.l_len = 0;
lk.l_type = F_WRLCK;
lk.l_whence = SEEK_SET;
if( 0 > fcntl( fd, F_SETLK, &lk ) )
{
if( EAGAIN == errno )
{
errnomsg( "another copy of %s is already running", getmyname() );
}
else
{
errnomsg( "failed to obtain lock on file: %s", path );
}
close( fd );
return -1;
}
ftruncate( fd, 0 );
snprintf( pid, sizeof pid, "%i\n", getpid() );
write( fd, pid, strlen( pid ) );
return fd;
}
int
getsock( const char * path )
{
struct sockaddr_un sun;
int fd;
fd = socket( PF_LOCAL, SOCK_STREAM, 0 );
if( 0 > fd )
{
errnomsg( "failed to create socket file: %s", path );
return -1;
}
bzero( &sun, sizeof sun );
sun.sun_family = AF_LOCAL;
strlcpy( sun.sun_path, path, sizeof sun.sun_path );
unlink( path );
if( 0 > bind( fd, ( struct sockaddr * )&sun, SUN_LEN( &sun ) ) )
{
/* bind can sometimes fail on the first call */
unlink( path );
if( 0 > bind( fd, ( struct sockaddr * )&sun, SUN_LEN( &sun ) ) )
{
errnomsg( "failed to bind socket file: %s", path );
close( fd );
return -1;
}
}
return fd;
}
void
exitcleanup( void )
{
if( 0 <= gl_sockfd )
{
unlink( gl_sockpath );
close( gl_sockfd );
}
if( 0 <= gl_lockfd )
{
unlink( gl_lockpath );
close( gl_lockfd );
}
}
void
setupsigs( struct event_base * base /* XXX */ UNUSED )
{
static struct event ev_int;
static struct event ev_quit;
static struct event ev_term;
signal_set( &ev_int, SIGINT, gotsig, NULL );
/* XXX event_base_set( base, &ev_int ); */
signal_add( &ev_int, NULL );
signal_set( &ev_quit, SIGINT, gotsig, NULL );
/* XXX event_base_set( base, &ev_quit ); */
signal_add( &ev_quit, NULL );
signal_set( &ev_term, SIGINT, gotsig, NULL );
/* XXX event_base_set( base, &ev_term ); */
signal_add( &ev_term, NULL );
signal( SIGPIPE, SIG_IGN );
signal( SIGHUP, SIG_IGN );
}
void
gotsig( int sig, short what UNUSED, void * arg UNUSED )
{
static int exiting = 0;
if( !exiting )
{
exiting = 1;
errmsg( "received fatal signal %i, attempting to exit cleanly", sig );
torrent_exit( 0 );
}
else
{
errmsg( "received fatal signal %i while exiting, exiting immediately",
sig );
signal( sig, SIG_DFL );
raise( sig );
}
}

112
daemon/errors.c Normal file
View File

@@ -0,0 +1,112 @@
/******************************************************************************
* $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 <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include "errors.h"
static void verrmsg( int, const char *, va_list );
static int gl_syslog = 0;
void
errsyslog( int useit )
{
gl_syslog = useit;
if( useit )
{
openlog( getmyname(), 0, LOG_DAEMON );
}
}
void
errmsg( const char * fmt, ... )
{
va_list ap;
va_start( ap, fmt );
verrmsg( -1, fmt, ap );
va_end( ap );
}
void
errnomsg( const char * fmt, ... )
{
va_list ap;
va_start( ap, fmt );
verrmsg( errno, fmt, ap );
va_end( ap );
}
void
verrmsg( int errnum, const char * fmt, va_list ap )
{
char buf[1024];
vsnprintf( buf, sizeof buf, fmt, ap );
if( gl_syslog )
{
if( 0 > errnum )
{
syslog( LOG_ERR, "%s", buf );
}
else
{
syslog( LOG_ERR, "%s: %s", buf, strerror( errnum ) );
}
}
else
{
if( 0 > errnum )
{
fprintf( stderr, "%s: %s\n", getmyname(), buf );
}
else
{
fprintf( stderr, "%s: %s: %s\n", getmyname(), buf,
strerror( errnum ) );
}
}
}
void
mallocmsg( size_t size )
{
if( 0 < size )
{
errmsg( "failed to allocate %i bytes of memory", ( int )size );
}
else
{
errmsg( "failed to allocate memory" );
}
}

35
daemon/errors.h Normal file
View File

@@ -0,0 +1,35 @@
/******************************************************************************
* $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.
*****************************************************************************/
#ifndef TR_DAEMON_ERRORS_H
#define TR_DAEMON_ERRORS_H
#include "misc.h"
void errsyslog( int );
void errmsg( const char *, ... ) PRINTF( 1, 2 );
void errnomsg( const char *, ... ) PRINTF( 1, 2 );
void mallocmsg( size_t );
#endif /* TR_DAEMON_ERRORS_H */

1214
daemon/ipc.c Normal file

File diff suppressed because it is too large Load Diff

168
daemon/ipc.h Normal file
View File

@@ -0,0 +1,168 @@
/******************************************************************************
* $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.
*****************************************************************************/
#ifndef TR_DAEMON_IPC_H
#define TR_DAEMON_IPC_H
#include <stdint.h>
/* yay for typedefs, we can't forward declare benc_val_t or tr_info_t
like with structs */
#include "bencode.h"
#include "transmission.h"
#define IPC_MIN_MSG_LEN ( 8 )
#define IPC_MAX_MSG_LEN ( 0x7fffffff - IPC_MIN_MSG_LEN )
enum ipc_msg
{
IPC_MSG_ADDMANYFILES = 0,
IPC_MSG_ADDONEFILE,
IPC_MSG_AUTOMAP,
IPC_MSG_AUTOSTART,
IPC_MSG_DIR,
IPC_MSG_DOWNLIMIT,
IPC_MSG_FAIL,
IPC_MSG_GETAUTOMAP,
IPC_MSG_GETAUTOSTART,
IPC_MSG_GETDIR,
IPC_MSG_GETDOWNLIMIT,
IPC_MSG_GETINFO,
IPC_MSG_GETINFOALL,
IPC_MSG_GETPEX,
IPC_MSG_GETPORT,
IPC_MSG_GETSTAT,
IPC_MSG_GETSTATALL,
IPC_MSG_GETSUP,
IPC_MSG_GETUPLIMIT,
IPC_MSG_LOOKUP,
IPC_MSG_INFO,
IPC_MSG_NOOP,
IPC_MSG_NOTSUP,
IPC_MSG_PEX,
IPC_MSG_PORT,
IPC_MSG_QUIT,
IPC_MSG_REMOVE,
IPC_MSG_REMOVEALL,
IPC_MSG_START,
IPC_MSG_STARTALL,
IPC_MSG_STAT,
IPC_MSG_STOP,
IPC_MSG_STOPALL,
IPC_MSG_OK,
IPC_MSG_SUP,
IPC_MSG_UPLIMIT,
IPC_MSG_VERSION,
IPC__MSG_COUNT
};
#define IPC_INF_COMMENT ( 1 << 0 )
#define IPC_INF_CREATOR ( 1 << 1 )
#define IPC_INF_DATE ( 1 << 2 )
#define IPC_INF_FILES ( 1 << 3 )
#define IPC_INF_HASH ( 1 << 4 )
#define IPC_INF_ID ( 1 << 5 )
#define IPC_INF_NAME ( 1 << 6 )
#define IPC_INF_PATH ( 1 << 7 )
#define IPC_INF_PRIVATE ( 1 << 8 )
#define IPC_INF_SAVED ( 1 << 9 )
#define IPC_INF_SIZE ( 1 << 10 )
#define IPC_INF_TRACKERS ( 1 << 11 )
#define IPC_INF__MAX ( 1 << 12 )
#define IPC_ST_COMPLETED ( 1 << 0 )
#define IPC_ST_DOWNSPEED ( 1 << 1 )
#define IPC_ST_DOWNTOTAL ( 1 << 2 )
#define IPC_ST_ERROR ( 1 << 3 )
#define IPC_ST_ERRMSG ( 1 << 4 )
#define IPC_ST_ETA ( 1 << 5 )
#define IPC_ST_ID ( 1 << 6 )
#define IPC_ST_PEERDOWN ( 1 << 7 )
#define IPC_ST_PEERFROM ( 1 << 8 )
#define IPC_ST_PEERTOTAL ( 1 << 9 )
#define IPC_ST_PEERUP ( 1 << 10 )
#define IPC_ST_RUNNING ( 1 << 11 )
#define IPC_ST_STATE ( 1 << 12 )
#define IPC_ST_SWARM ( 1 << 13 )
#define IPC_ST_TRACKER ( 1 << 14 )
#define IPC_ST_TKDONE ( 1 << 15 )
#define IPC_ST_TKLEECH ( 1 << 16 )
#define IPC_ST_TKSEED ( 1 << 17 )
#define IPC_ST_UPSPEED ( 1 << 18 )
#define IPC_ST_UPTOTAL ( 1 << 19 )
#define IPC_ST__MAX ( 1 << 20 )
struct ipc_funcs;
struct ipc_info;
struct strlist;
struct ipc_info
{
struct ipc_funcs * funcs;
int vers;
};
#define HASVERS( info ) ( 0 < (info)->vers )
typedef void ( *trd_msgfunc )( enum ipc_msg, benc_val_t *, int64_t, void * );
/* any of these functions that can fail may set errno for any of the
errors set by malloc() or calloc() */
/* setup */
struct ipc_funcs * ipc_initmsgs ( void );
int ipc_addmsg ( struct ipc_funcs *, enum ipc_msg, trd_msgfunc );
void ipc_setdefmsg( struct ipc_funcs *, trd_msgfunc );
void ipc_freemsgs ( struct ipc_funcs * );
void ipc_newcon ( struct ipc_info *, struct ipc_funcs * );
/* message creation */
/* sets errno to EPERM if requested message not supported by protocol vers */
benc_val_t * ipc_initval ( struct ipc_info *, enum ipc_msg, int64_t,
benc_val_t *, int );
uint8_t * ipc_mkval ( benc_val_t *, size_t * );
uint8_t * ipc_mkempty ( struct ipc_info *, size_t *, enum ipc_msg,
int64_t );
uint8_t * ipc_mkint ( struct ipc_info *, size_t *, enum ipc_msg, int64_t,
int64_t );
uint8_t * ipc_mkstr ( struct ipc_info *, size_t *, enum ipc_msg, int64_t,
const char * );
uint8_t * ipc_mkvers ( size_t * );
uint8_t * ipc_mkgetinfo( struct ipc_info *, size_t *, enum ipc_msg, int64_t,
int, const int * );
int ipc_addinfo ( benc_val_t *, int, tr_info_t *, int );
int ipc_addstat ( benc_val_t *, int, tr_info_t *, tr_stat_t *, int );
/* sets errno to EINVAL on parse error or
EPERM for unsupported protocol version */
ssize_t ipc_parse ( struct ipc_info *, uint8_t *, ssize_t, void * );
/* misc info functions, these will always succeed */
int ipc_havemsg ( struct ipc_info *, enum ipc_msg );
enum ipc_msg ipc_msgid ( struct ipc_info *, const char * );
int ipc_havetags ( struct ipc_info * );
int ipc_infotypes( enum ipc_msg, benc_val_t * );
const char * ipc_infoname ( enum ipc_msg, int );
#endif /* TR_DAEMON_IPC_H */

454
daemon/ipcproto.txt Normal file
View File

@@ -0,0 +1,454 @@
It is assumed the reader is familiar with bencoding, as described in
the BitTorrent protocol specification at
http://www.bittorrent.org/protocol.html
Dictionary keys used below will be enclosed in quotation marks, these
are used for clarity and are not part of the actual key.
The IPC protocol is used to allow processes to control or retrieve
information from a Transmission frontend, such as transmission-daemon
or transmission-gtk. This communication is done over a unix-domain
socket file, such as ~/.transmission/daemon/socket. In this document
the Transmission frontend will be referred to as the server and the
process connecting to it as the client.
Once a client connects to the server's socket, messages may be
exchanged until either end closes the connection. Messages contain an
32-bit payload length, encoded as 8 bytes of ASCII hexidecimal,
followed by the payload. Upper, lower, or mixed case for the length
are all accpetable and must be handled correctly. Payload lengths
greater than 2^31 - 8 (ie: 2147483640) are not allowed.
For version 1, the message payload is a bencoded dictionary, the
valid keys and value formats for which are described below. Multiple
keys may be used in one message.
An example version 1 message:
0000000Ed4:porti9090ee
For version 2 the message payload is a bencoded list containing a
message id string followed by a bencoded value, the format of which is
the same for version 1. The value may be followed by an optional
bencoded integer, this is a message tag and is described in more
detail below.
An example version 2 message:
0000001El12:get-info-alll4:hashee
The same message with a tag:
00000021l12:get-info-alll4:hashei5ee
Once the connection is made both the client and server must send a
version 1 style message (ie: the payload is a dictionary and may not
contain a tag) with the dictionary key "version" and a value formatted
as described below. The version should be the first but not
necessarily only key in the dictionary. Any other keys should be
ignored and not processed as messages. Neither the client nor the
server should wait to receive a version message before sending one, it
must be sent immediately. No other messages should be sent until the
version is received.
The version value should be a bencoded dictionary containing two keys,
"max" and "min". These are the minimum and maximum supported protocol
versions, respectively. Communication will take place using the
highest protocol version supported by both the client and the server,
and is not possible at all if there is no common version. A client may
receive a version value that is an integer instead of a dictionary
with "min" and "max" keys. This deprecated version format indicates
the only version supported by the server.
An example message containing minimum and maximum versions 1 and 2:
0000001Dd7:versiond3:mini1e3:maxi2eee
Tagged messages, only supported in version 2, allow a client to tag a
message with a positive non-zero integer. The server is then required
to send a response to the message even if none would normally be
required, and to tag the response with the same integer. When the
server receives a tagged message it must send exactly one message back
with the same tag. The client is allowed to use the same tag for
multiple messages, even if a response to the first is not received
before the second it sent. If a tagged message does not normally
require a response then a "succeeded", "failed" or "not-supported"
message will be sent back.
An example tagged message and response:
00000010l5:startli8eei15ee
00000011l8:succeeded0:i15ee
Some example sessions, including version handshake, are found at the
end of this file.
Dictionary keys are encoded in UTF-8 and case sensitive. Any character
except a NUL (0x00) is valid. A server or client need not support
every possible key and should silently ignore any that it does not
understand.
If a reference to a boolean is seen, it should be taken to mean an
integer with a value of 0 representing false, 1 representing true, and
any other value undefined.
Individual torrents are identified by a unique integer. This integer
is only valid for the current connection and may be invalid or refer
to another torrent in a future connection. If a torrent is closed it's
ID will never be reused to refer to another torrent for at least the
duration of the connection. Negative integers or 0 are not valid IDs.
A list of keys and the format of their values follows. Also listed is
the minimum protocol version that the key may be used with.
Key: "addfiles"
Version: 1
Format: list of strings
Example: 8:addfilesl21:/torrents/foo.torrent20:/home/me/bar.torrente
Details: Each string is the absolute path to a torrent metainfo file
for the server to add. Note that whether or not the torrent
metainfo file is copied (allowing the original to be moved or
deleted safely) is implementation dependent and may not
currently be known or changed with this protocol.
Key: "addfile-detailed"
Version: 2
Format: dict
Example: 16:addfile-detailedde
Details: Dictionary containing information about a torrent for the
server to add. Valid keys include:
"file" string, filename of torrent metainfo file
"data" string, contents of a torrent metainfo file
"directory" string, directory for data files for the torrent
"autostart" boolean, start the torrent automatically
Either "file" or "data" is required, but both are not allowed.
Key: "automap"
Version: 2
Format: boolean
Example: 7:automapi1e
Details: Enable (1) or disable (0) automatic port mapping on the
server. Other integer values will likely be treated as 1 but
this shold not be relied upon.
Key: "autostart"
Version: 2
Format: boolean
Example: 9:autostarti0e
Details: Enable (1) or disable (0) automatic starting of new torrents
added via "addfiles" message.
Key: "directory"
Version: 2
Format: string
Example: 9:directory21:/home/roger/downloads
Details: Set the default directory used for any torrents added in the future.
Key: "downlimit"
Version: 2
Format: int
Example: 9:downlimiti100e
Details: Set the server's download limit in kilobytes per second.
Negative values are interpreted as no limit.
Key: "failed"
Version: 2
Format: string
Example: 6:failed17:permission denied
Details: Sent in response to a tagged message to indicate failure.
Key: "get-automap"
Version: 2
Format: value is ignored
Example: 11:get-automap0:
Details: Requests that an "automap" message be sent back.
Key: "get-autostart"
Version: 2
Format: value is ignored
Example: 13:get-autostart0:
Details: Requests that an "autostart" message be sent back.
Key: "get-directory"
Version: 2
Format: value is ignored
Example: 13:get-directory
Details: Requests that an "directory" message be sent back.
Key: "get-downlimit"
Version: 2
Format: value is ignored
Example: 13:get-downlimit0:
Details: Requests that a "downlimit" message be sent back.
Key: "get-info"
Version: 2
Format: dict with keys "id" and "type" for lists of ints and strings
Example: 8:get-infod2:idli4ei7ei2ee4:typel4:hash4:nameee
Details: Requests that the server send back an "info" message with
info on all the torrent IDs in "id". The "type" key requests
what info will be returned. See below for valid values to use
here. Since the torrent ID is always included in an "info"
message an empty or missing "type" key will cause only the ID
to be returned. An "info" message will always be sent back,
even if it is empty.
Key: "get-info-all"
Version: 2
Format: list of strings
Example: 12:get-info-alll4:hash4:namee
Details: Same as "getinfo" message with all torrent IDs specified.
Key: "get-pex"
Version: 2
Format: value is ignored
Example: 7:get-pex0:
Details: Requests that a "pex" message be sent back.
Key: "get-port"
Version: 2
Format: value is ignored
Example: 8:get-port0:
Details: Requests that a "port" message be sent back.
Key: "get-status"
Version: 2
Format: dict with keys "id" and "type" for lists of ints and strings
Example: 10:get-statusd2:idli4ei7ei2ee4:typel4:hash4:nameee
Details: Same as "get-info" message except status type strings are used
instead and the server sends back a "status" message.
Key: "get-status-all"
Version: 2
Format: list of strings
Example: 14:get-status-alll4:hash4:namee
Details: Same as "get-status" message with all torrent IDs specified.
Key: "get-supported"
Version: 2
Format: list of strings
Example: 13:get-supportedl6:lookup8:get-porte
Details: Request that a "supported" message be returned with whichever
of the given message keys are supported.
Key: "get-uplimit"
Version: 2
Format: value is ignored
Example: 11:get-uplimit0:
Details: Requests that an "uplimit" message be sent back.
Key: "lookup"
Version: 2
Format: list of strings
Example: 6:lookupl40:0f16ea6965ee5133ea4dbb1e7f516e9fcf3d899ee
Details: Request that the server send back an "info" message with "id"
and "hash" keys for any torrents with the given hashes.
Key: "info"
Version: 2
Format: list of dictionaries
Example: 4:infold2:idi3e4:name3:fooed2:idi9e4:name3:baree
Details: A list containing information for several torrents. The
dictionaries always contain at least an "id" key with the
integer ID for the torrent, other possible values are listed
below.
Key: "noop"
Version: 2
Format: value is ignored
Example: 4:noop0:
Details: This does nothing but keep the connection alive. With a tag
it may be used as a ping.
Key: "not-supported"
Version: 2
Format: value is ignored
Example: 13:not-supported0:
Details: Sent in response to a tagged message to indicated that the
message was not supported.
Key: "pex"
Version: 2
Format: boolean
Example: 3:pexi0e
Details: Enables or disables peer exchange.
Key: "port"
Version: 2
Format: int between 0 and 65535
Example: 4:porti9090e
Details: Change the port the server uses to listen for incoming peer
connections.
Key: "quit"
Version: 1
Format: value is ignored
Example: 4:quit0:
Details: Cause the server to quit.
Key: "remove"
Version: 2
Format: list of torrent ID ints
Example: 5:removeli3ei8ei6ee
Details: Stop and remove the specified torrents. Note that whether or
not the downloaded data or the original torrent files will be
removed is implementation dependent and may not currently be
known or changed with this protocol. If a saved copy of the
torrent file has been made then it will always be deleted.
Key: "remove-all"
Version: 2
Format: value is ignored
Example: 10:remove-all0:
Details: Like "remove" with all torrent IDs specified.
Key: "start"
Version: 2
Format: list of torrent ID ints
Example: 5:startli3ei8ei6ee
Details: List of torrent IDs to start.
Key: "start-all"
Version: 2
Format: value is ignored
Example: 9:start-all0:
Details: Start all torrents.
Key: "status"
Version: 2
Format: list of dictionaries
Example: 4:infold2:idi3e4:name3:fooed2:idi9e4:name3:baree
Details: Same as "info" message except status type keys are used.
Key: "stop"
Version: 2
Format: list of torrent ID ints
Example: 4:stopli3ei8ei6ee
Details: List of torrent IDs to stop.
Key: "stop-all"
Version: 2
Format: value is ignored
Example: 8:stop-all0:
Details: Stop all torrents.
Key: "succeeded"
Version: 2
Format: value is ignored
Example: 8:succeeded0:
Details: This is used to indicate that a tagged message was processed
successfully.
Key: "supported"
Version: 2
Format: list of strings
Example: 9:supportedl6:lookupe
Details: Sent in response to a "get-supported" message, indicates that
the given messages ate supported.
Key: "uplimit"
Version: 2
Format: int
Example: 7:uplimiti20e
Details: Set the server's upload limit in kilobytes per second.
Negative values are interpreted as no limit.
Info types for "get-info" and "info" messages. The only type for which
support is mandatory is "id".
"id" integer, torrent's ID for this connection
"hash" SHA-1 info hash as a 40-char hex string
"name" string, torrent name
"path" string, path to .torrent file
"saved" boolean, true if a copy of this torrent was saved
"private" boolean, true if the torrent is private
"trackers" a list of lists of dictionaries containing tracker info:
"address" string, hostname or ip address of tracker
"port" integer, port for tracker
"announce" string, announce url on tracker
"scrape" string, scrape url on tracker, may be absent
"comment" string, comment from torrent file
"creator" string, creator of torrent file
"date" integer, date of torrent creation (unix time_t format)
"size" integer, total size of all files in bytes
"files" list of dictionaries for the files in this torrent:
"name" string, name of file
"size" integer, size of file in bytes
Status types for "get-status" and "status" messages. The only type for
which support is mandatory is "id".
"completed" integer, bytes of data downloaded and verified
"download-speed" integer, download speed in bytes per second
"download-total" integer, total bytes downloaded so far
"error" string, one of the following:
"assert" something happened that shouldn't
"io-parent" I dunno
"io-permissions" filesystem permission error probably
"io-other" other filesystem i/o error
"tracker-error" tracker returned error message
"tracker-warning" tracker returned warning message
"other" other error
zero-length or missing string indicates no error
"error-message" string, printable error message
"eta" integer, estimated seconds until downloading is finished
"id" integer, torrent's ID for this connection
"peers-downloading" integer, peers downloading from us
"peers-from" dict with the following int keys, peer connection sources:
"incoming" peers connected to our listening port
"tracker" peers discovered from tracker
"cache" peers retrieved from on-disk cache
"pex" peers discovered via peer exchange
"peers-total" integer, total connected peers
"peers-uploading" integer, peers uploading to us
"running" boolean, false if torrent is stopped or stopping
"state" string, one of the following:
"checking" performing hash check on file data
"downloading" downloading file data
"seeding" seeding file data to peers
"stopping" contacting tracker to send 'stopped' event
"paused" torrent is not active
"swarm-speed" integer, swarm speed in bytes per second
"tracker" dict with the following keys, current active tracker
"address" string, hostname or ip address of tracker
"port" integer, port for tracker
"announce" string, tracker announce url
"scrape" string, tracker scrape url, may be absent
"scrape-completed" integer, total completed peers as reported by tracker
"scrape-leechers" integer, current leechers as reported by tracker
"scrape-seeders" integer, current, seeders as reported by tracker
"upload-speed" integer, upload speed in bytes per second
"upload-total" integer, total bytes uploaded so far
Examples:
Data from the client to the server is prefixed with >>> and from
server to client with <<<. These prefixes and newlines are added for
clarity, they are not actually sent over the socket.
Quit the server. Note that this is a version 1 client and so version 1
messages are used. The value for the quit message here is i0e instead
of 0: as used above, however since it it unused any value is allowed.
>>> 0000001Dd7:versiond3:mini1e3:maxi1eee
<<< 0000001Dd7:versiond3:mini1e3:maxi2eee
>>> 0000000Bd4:quiti0ee
Pause all torrents and disable automapping. Note the server happens to
have sendt it's version before the client.
<<< 0000001Dd7:versiond3:mini1e3:maxi2eee
>>> 0000001Dd7:versiond3:mini1e3:maxi2eee
>>> 0000000El8:stop-all0:e
Change upload and download limits with tagged responses. Note that the
server is allowed to return responses out of order, even when the
messages are not tagged.
>>> 0000001Dd7:versiond3:mini1e3:maxi2eee
<<< 0000001Dd7:versiond3:mini1e3:maxi2eee
>>> 00000017l9:downlimiti100ei100el
>>> 00000017l9:downlimiti100ei101el
<<< 00000014l8:succeeded0:i101el
<<< 00000014l8:succeeded0:i100el

203
daemon/misc.c Normal file
View File

@@ -0,0 +1,203 @@
/******************************************************************************
* $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/stat.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "errors.h"
#include "misc.h"
#include "transmission.h"
#include "trcompat.h"
static void pushdir ( char *, const char *, size_t );
static char gl_myname[256];
void
setmyname( const char * argv0 )
{
const char * name;
name = strrchr( argv0, '/' );
if( NULL == name || '\0' == *(++name) )
{
name = argv0;
}
strlcpy( gl_myname, name, sizeof gl_myname );
}
const char *
getmyname( void )
{
return gl_myname;
}
void
pushdir( char * path, const char * file, size_t size )
{
size_t off;
off = strlen( path );
if( 0 < off && off + 1 < size && '/' != path[off-1] )
{
path[off] = '/';
path[off+1] = '\0';
}
strlcat( path, file, size );
}
void
confpath( char * buf, size_t len, const char * file, enum confpathtype type )
{
const char * typestr;
switch( type )
{
case CONF_PATH_TYPE_DAEMON:
typestr = "daemon";
break;
case CONF_PATH_TYPE_GTK:
typestr = "gtk";
break;
default:
assert( 0 );
break;
}
strlcpy( buf, tr_getPrefsDirectory(), len );
pushdir( buf, typestr, len );
if( NULL != file )
{
pushdir( buf, file, len );
}
}
void
absolutify( char * buf, size_t len, const char * path )
{
size_t off;
if( '/' == path[0] )
{
strlcpy( buf, path, len );
return;
}
getcwd( buf, len );
off = strlen( buf );
if( 0 < off && len > off + 1 && '/' != buf[off] )
{
buf[off] = '/';
off++;
buf[off] = '\0';
}
strlcat( buf, path, len );
}
int
writefile( const char * name, uint8_t * buf, ssize_t len )
{
int fd;
ssize_t res;
fd = open( name, O_WRONLY | O_CREAT | O_TRUNC, 0666 );
if( 0 > fd )
{
errnomsg( "failed to open %s for writing", name );
return -1;
}
res = write( fd, buf, len );
if( 0 > res )
{
errnomsg( "failed to write to %s", name );
return -1;
}
if( len > res )
{
errmsg( "failed to write all data to %s", name );
return -1;
}
close( fd );
return 0;
}
uint8_t *
readfile( const char * name, size_t * len )
{
struct stat sb;
int fd;
uint8_t * buf;
ssize_t res;
fd = open( name, O_RDONLY );
if( 0 > fd )
{
if( ENOENT != errno )
{
errnomsg( "failed to open %s for reading", name );
}
return NULL;
}
if( 0 > fstat( fd, &sb ) )
{
errnomsg( "failed to stat %s", name );
return NULL;
}
buf = malloc( sb.st_size );
if( NULL == buf )
{
mallocmsg( sb.st_size );
return NULL;
}
res = read( fd, buf, sb.st_size );
if( 0 > res )
{
errnomsg( "failed to read from %s", name );
free( buf );
return NULL;
}
if( res < sb.st_size )
{
errmsg( "failed to read all data from %s", name );
free( buf );
return NULL;
}
close( fd );
*len = res;
return buf;
}

139
daemon/misc.h Normal file
View File

@@ -0,0 +1,139 @@
/******************************************************************************
* $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.
*****************************************************************************/
#ifndef TR_DAEMON_MISC_H
#define TR_DAEMON_MISC_H
#include <sys/types.h>
#include <inttypes.h>
#include <limits.h>
#include "bsdqueue.h"
#define CONF_FILE_LOCK "lock"
#define CONF_FILE_SOCKET "socket"
#define CONF_FILE_STATE "state"
enum confpathtype
{
CONF_PATH_TYPE_DAEMON,
CONF_PATH_TYPE_GTK,
};
struct bufferevent;
#ifdef __GNUC__
# define UNUSED __attribute__((unused))
#else
# define UNUSED
#endif
#ifdef __GNUC__
# define PRINTF( fmt, args ) __attribute__((format (printf, fmt, args)))
#else
# define PRINTF( fmt, args )
#endif
#define ARRAYLEN( ary ) ( sizeof( ary ) / sizeof( (ary)[0] ) )
#ifndef MIN
#define MIN( aa, bb ) ( (aa) < (bb) ? (aa) : (bb) )
#endif
#ifndef MAX
#define MAX( aa, bb ) ( (aa) > (bb) ? (aa) : (bb) )
#endif
#undef NULL
#define NULL ( ( void * )0 )
#define TORRENT_ID_VALID( id ) ( 0 < (id) && INT_MAX > (id) )
#define SAFEFREE( ptr ) \
do \
{ \
int saved = errno; \
free( ptr ); \
errno = saved; \
} \
while( 0 )
#define SAFEFREESTRLIST( ptr ) \
do \
{ \
int saved = errno; \
FREESTRLIST( ptr ); \
errno = saved; \
} \
while( 0 )
#define SAFEBENCFREE( val ) \
do \
{ \
int saved = errno; \
tr_bencFree( val ); \
errno = saved; \
} \
while( 0 )
#define INTCMP_FUNC( name, type, id ) \
int \
name( struct type * _icf_first, struct type * _icf_second ) \
{ \
if( _icf_first->id < _icf_second->id ) \
{ \
return -1; \
} \
else if( _icf_first->id > _icf_second->id ) \
{ \
return 1; \
} \
else \
{ \
return 0; \
} \
}
struct stritem
{
char * str;
SLIST_ENTRY( stritem ) next;
};
SLIST_HEAD( strlist, stritem );
#define FREESTRLIST( _fl_head ) \
while( !SLIST_EMPTY( _fl_head ) ) \
{ \
struct stritem * _fl_dead = SLIST_FIRST( _fl_head ); \
SLIST_REMOVE_HEAD( _fl_head, next ); \
free( _fl_dead->str ); \
free( _fl_dead ); \
}
void setmyname ( const char * );
const char * getmyname ( void );
void confpath ( char *, size_t, const char *, enum confpathtype );
void absolutify( char *, size_t, const char * );
int writefile ( const char *, uint8_t *, ssize_t );
uint8_t * readfile ( const char *, size_t * );
#endif /* TR_DAEMON_MISC_H */

324
daemon/proxy.c Normal file
View File

@@ -0,0 +1,324 @@
/******************************************************************************
* $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/param.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/un.h>
#include <event.h>
#include <getopt.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "errors.h"
#include "misc.h"
#include "transmission.h"
static void usage ( const char *, ... );
static enum confpathtype readargs ( int, char ** );
static int makesock ( enum confpathtype );
static void inread ( struct bufferevent *, void * );
static void noop ( struct bufferevent *, void * );
static void inerr ( struct bufferevent *, short, void * );
static void wtf ( struct bufferevent *, void * );
static void outerr ( struct bufferevent *, short, void * );
static void sockread ( struct bufferevent *, void * );
static void sockwrite( struct bufferevent *, void * );
static void sockerr ( struct bufferevent *, short, void * );
static struct bufferevent * gl_in = NULL;
static struct bufferevent * gl_out = NULL;
static struct bufferevent * gl_sock = NULL;
int
main( int argc, char ** argv )
{
struct event_base * base;
enum confpathtype type;
int sockfd;
setmyname( argv[0] );
type = readargs( argc, argv );
base = event_init();
sockfd = makesock( type );
if( 0 > sockfd )
{
return EXIT_FAILURE;
}
gl_in = bufferevent_new( STDIN_FILENO, inread, noop, inerr, NULL );
if( NULL == gl_in )
{
errnomsg( "failed to set up event buffer for stdin" );
return EXIT_FAILURE;
}
/* XXX bufferevent_base_set( base, gl_in ); */
bufferevent_enable( gl_in, EV_READ );
bufferevent_disable( gl_in, EV_WRITE );
gl_out = bufferevent_new( STDOUT_FILENO, wtf, noop, outerr, NULL );
if( NULL == gl_in )
{
errnomsg( "failed to set up event buffer for stdin" );
return EXIT_FAILURE;
}
/* XXX bufferevent_base_set( base, gl_out ); */
bufferevent_disable( gl_out, EV_READ );
bufferevent_enable( gl_out, EV_WRITE );
gl_sock = bufferevent_new( sockfd, sockread, sockwrite, sockerr, NULL );
if( NULL == gl_in )
{
errnomsg( "failed to set up event buffer for stdin" );
return EXIT_FAILURE;
}
/* XXX bufferevent_base_set( base, gl_sock ); */
bufferevent_enable( gl_sock, EV_READ );
bufferevent_enable( gl_sock, EV_WRITE );
event_dispatch();
/* XXX event_base_dispatch( base ); */
return EXIT_FAILURE;
}
void
usage( const char * msg, ... )
{
va_list ap;
if( NULL != msg )
{
printf( "%s: ", getmyname() );
va_start( ap, msg );
vprintf( msg, ap );
va_end( ap );
printf( "\n" );
}
printf(
"usage: %s [options] [files]...\n"
"\n"
"Transmission %s (r%d) http://transmission.m0k.org/\n"
"A free, lightweight BitTorrent client with a simple, intuitive interface.\n"
"\n"
" -h --help Display this message and exit\n"
" -t --type daemon Use the daemon frontend, transmission-daemon\n"
" -t --type gtk Use the GTK+ frontend, transmission-gtk\n",
getmyname(), VERSION_STRING, VERSION_REVISION );
exit( EXIT_SUCCESS );
}
enum confpathtype
readargs( int argc, char ** argv )
{
char optstr[] = "ht:";
struct option longopts[] =
{
{ "help", no_argument, NULL, 'h' },
{ "type", required_argument, NULL, 't' },
{ NULL, 0, NULL, 0 }
};
enum confpathtype type;
int opt;
type = CONF_PATH_TYPE_DAEMON;
while( 0 <= ( opt = getopt_long( argc, argv, optstr, longopts, NULL ) ) )
{
switch( opt )
{
case 't':
if( 0 == strcasecmp( "daemon", optarg ) )
{
type = CONF_PATH_TYPE_DAEMON;
}
else if( 0 == strcasecmp( "gtk", optarg ) )
{
type = CONF_PATH_TYPE_GTK;
}
else
{
usage( "invalid type: %s", optarg );
}
break;
default:
usage( NULL );
break;
}
}
return type;
}
int
makesock( enum confpathtype type )
{
struct sockaddr_un sun;
int fd;
bzero( &sun, sizeof sun );
sun.sun_family = AF_LOCAL;
confpath( sun.sun_path, sizeof sun.sun_path, CONF_FILE_SOCKET, type );
fd = socket( AF_UNIX, SOCK_STREAM, 0 );
if( 0 > fd )
{
errnomsg( "failed to create socket" );
return -1;
}
if( 0 > connect( fd, ( struct sockaddr * )&sun, SUN_LEN( &sun ) ) )
{
errnomsg( "failed to connect to socket file: %s", sun.sun_path );
close( fd );
return -1;
}
return fd;
}
void
inread( struct bufferevent * ev UNUSED, void * arg UNUSED )
{
bufferevent_write_buffer( gl_sock, EVBUFFER_INPUT( gl_in ) );
}
void
noop( struct bufferevent * ev UNUSED, void * arg UNUSED )
{
}
void
inerr( struct bufferevent * ev UNUSED, short what, void * arg UNUSED )
{
if( EVBUFFER_EOF & what )
{
bufferevent_free( gl_in );
gl_in = NULL;
sockwrite( NULL, NULL );
return;
}
if( EVBUFFER_TIMEOUT & what )
{
errmsg( "timed out reading from stdin" );
}
else if( EVBUFFER_READ & what )
{
errmsg( "read error on stdin" );
}
else if( EVBUFFER_ERROR & what )
{
errmsg( "error on stdin" );
}
else
{
errmsg( "unknown error on stdin: 0x%x", what );
}
exit( EXIT_FAILURE );
}
void
wtf( struct bufferevent * ev, void * arg UNUSED )
{
/* this shouldn't happen, but let's drain the buffer anyway */
evbuffer_drain( EVBUFFER_INPUT( ev ),
EVBUFFER_LENGTH( EVBUFFER_INPUT( ev ) ) );
}
void
outerr( struct bufferevent * ev UNUSED, short what, void * arg UNUSED )
{
if( EVBUFFER_TIMEOUT & what )
{
errmsg( "timed out writing to stdout" );
}
else if( EVBUFFER_WRITE & what )
{
errmsg( "write error on stdout" );
}
else if( EVBUFFER_ERROR & what )
{
errmsg( "error on client stdout" );
}
else
{
errmsg( "unknown error on stdout connection: 0x%x", what );
}
exit( EXIT_FAILURE );
}
void
sockread( struct bufferevent * ev UNUSED, void * arg UNUSED )
{
bufferevent_write_buffer( gl_out, EVBUFFER_INPUT( gl_sock ) );
}
void
sockwrite( struct bufferevent * ev UNUSED, void * arg UNUSED )
{
if( NULL == gl_in && 0 == EVBUFFER_LENGTH( EVBUFFER_OUTPUT( gl_sock ) ) )
{
exit( EXIT_SUCCESS );
}
}
void
sockerr( 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( EXIT_FAILURE );
}

1027
daemon/remote.c Normal file

File diff suppressed because it is too large Load Diff

960
daemon/server.c Normal file
View File

@@ -0,0 +1,960 @@
/******************************************************************************
* $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 <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "bsdtree.h"
#include "bencode.h"
#include "errors.h"
#include "ipc.h"
#include "misc.h"
#include "server.h"
#include "torrents.h"
/* time out clients after this many seconds */
#define CLIENT_TIMEOUT ( 60 )
struct client
{
int fd;
struct bufferevent * ev;
struct ipc_info ipc;
RB_ENTRY( client ) link;
};
RB_HEAD( allclients, client );
static void newclient( int, short, void * );
static void noop ( struct bufferevent *, void * );
static void byebye ( struct bufferevent *, short, void * );
static void doread ( struct bufferevent *, void * );
static int queuemsg ( struct client *, uint8_t *, size_t );
static void msgresp ( struct client *, int64_t, enum ipc_msg );
static void defmsg ( enum ipc_msg, benc_val_t *, int64_t, void * );
static void noopmsg ( enum ipc_msg, benc_val_t *, int64_t, void * );
static void addmsg1 ( enum ipc_msg, benc_val_t *, int64_t, void * );
static void addmsg2 ( enum ipc_msg, benc_val_t *, int64_t, void * );
static void quitmsg ( enum ipc_msg, benc_val_t *, int64_t, void * );
static void intmsg ( enum ipc_msg, benc_val_t *, int64_t, void * );
static void strmsg ( enum ipc_msg, benc_val_t *, int64_t, void * );
static void infomsg ( enum ipc_msg, benc_val_t *, int64_t, void * );
static int addinfo ( benc_val_t *, int, int );
static int addstat ( benc_val_t *, int, int );
static void tormsg ( enum ipc_msg, benc_val_t *, int64_t, void * );
static void lookmsg ( enum ipc_msg, benc_val_t *, int64_t, void * );
static void prefmsg ( enum ipc_msg, benc_val_t *, int64_t, void * );
static void supmsg ( enum ipc_msg, benc_val_t *, int64_t, void * );
static int clientcmp( struct client *, struct client * );
RB_GENERATE_STATIC( allclients, client, link, clientcmp )
INTCMP_FUNC( clientcmp, client, ev )
static struct event_base * gl_base = NULL;
static struct ipc_funcs * gl_tree = NULL;
static int gl_exiting = 0;
static struct allclients gl_clients = RB_INITIALIZER( &gl_clients );
int
server_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_ADDMANYFILES, addmsg1 ) ||
0 > ipc_addmsg( gl_tree, IPC_MSG_ADDONEFILE, addmsg2 ) ||
0 > ipc_addmsg( gl_tree, IPC_MSG_AUTOMAP, intmsg ) ||
0 > ipc_addmsg( gl_tree, IPC_MSG_AUTOSTART, intmsg ) ||
0 > ipc_addmsg( gl_tree, IPC_MSG_DOWNLIMIT, intmsg ) ||
0 > ipc_addmsg( gl_tree, IPC_MSG_DIR, strmsg ) ||
0 > ipc_addmsg( gl_tree, IPC_MSG_GETAUTOMAP, prefmsg ) ||
0 > ipc_addmsg( gl_tree, IPC_MSG_GETAUTOSTART, prefmsg ) ||
0 > ipc_addmsg( gl_tree, IPC_MSG_GETDOWNLIMIT, prefmsg ) ||
0 > ipc_addmsg( gl_tree, IPC_MSG_GETDIR, prefmsg ) ||
0 > ipc_addmsg( gl_tree, IPC_MSG_GETINFO, infomsg ) ||
0 > ipc_addmsg( gl_tree, IPC_MSG_GETINFOALL, infomsg ) ||
0 > ipc_addmsg( gl_tree, IPC_MSG_GETPEX, prefmsg ) ||
0 > ipc_addmsg( gl_tree, IPC_MSG_GETPORT, prefmsg ) ||
0 > ipc_addmsg( gl_tree, IPC_MSG_GETSTAT, infomsg ) ||
0 > ipc_addmsg( gl_tree, IPC_MSG_GETSTATALL, infomsg ) ||
0 > ipc_addmsg( gl_tree, IPC_MSG_GETUPLIMIT, prefmsg ) ||
0 > ipc_addmsg( gl_tree, IPC_MSG_GETSUP, supmsg ) ||
0 > ipc_addmsg( gl_tree, IPC_MSG_LOOKUP, lookmsg ) ||
0 > ipc_addmsg( gl_tree, IPC_MSG_NOOP, noopmsg ) ||
0 > ipc_addmsg( gl_tree, IPC_MSG_PEX, intmsg ) ||
0 > ipc_addmsg( gl_tree, IPC_MSG_PORT, intmsg ) ||
0 > ipc_addmsg( gl_tree, IPC_MSG_QUIT, quitmsg ) ||
0 > ipc_addmsg( gl_tree, IPC_MSG_REMOVE, tormsg ) ||
0 > ipc_addmsg( gl_tree, IPC_MSG_REMOVEALL, tormsg ) ||
0 > ipc_addmsg( gl_tree, IPC_MSG_START, tormsg ) ||
0 > ipc_addmsg( gl_tree, IPC_MSG_STARTALL, tormsg ) ||
0 > ipc_addmsg( gl_tree, IPC_MSG_STOP, tormsg ) ||
0 > ipc_addmsg( gl_tree, IPC_MSG_STOPALL, tormsg ) ||
0 > ipc_addmsg( gl_tree, IPC_MSG_UPLIMIT, intmsg ) )
{
return -1;
}
ipc_setdefmsg( gl_tree, defmsg );
return 0;
}
int
server_listen( int fd )
{
struct event * ev;
int flags;
assert( NULL != gl_base );
flags = fcntl( fd, F_GETFL );
if( 0 > flags )
{
errnomsg( "failed to get flags on socket" );
return -1;
}
if( 0 > fcntl( fd, F_SETFL, flags | O_NONBLOCK ) )
{
errnomsg( "failed to set flags on socket" );
return -1;
}
if( 0 > listen( fd, 5 ) )
{
errnomsg( "failed to listen on socket" );
return -1;
}
ev = malloc( sizeof *ev );
if( NULL == ev )
{
mallocmsg( sizeof *ev );
return -1;
}
event_set( ev, fd, EV_READ | EV_PERSIST, newclient, ev );
/* XXX event_base_set( gl_base, ev ); */
event_add( ev, NULL );
return 0;
}
void
newclient( int fd, short event UNUSED, void * arg )
{
struct sockaddr_un sun;
struct client * client, * old;
socklen_t socklen;
struct bufferevent * clev;
int clfd;
size_t buflen;
uint8_t * buf;
if( gl_exiting )
{
event_del( arg );
return;
}
for( ;; )
{
client = calloc( 1, sizeof *client );
if( NULL == client )
{
mallocmsg( sizeof *client );
return;
}
socklen = sizeof sun;
clfd = accept( fd, ( struct sockaddr * )&sun, &socklen );
if( 0 > clfd )
{
if( EWOULDBLOCK != errno && EAGAIN != errno &&
ECONNABORTED != errno )
{
errnomsg( "failed to accept ipc connection" );
}
free( client );
break;
}
clev = bufferevent_new( clfd, doread, noop, byebye, client );
if( NULL == clev )
{
close( clfd );
free( client );
mallocmsg( -1 );
return;
}
/* XXX bufferevent_base_set( gl_base, clev ); */
bufferevent_settimeout( clev, CLIENT_TIMEOUT, CLIENT_TIMEOUT );
client->fd = clfd;
ipc_newcon( &client->ipc, gl_tree );
client->ev = clev;
old = RB_INSERT( allclients, &gl_clients, client );
assert( NULL == old );
bufferevent_enable( clev, EV_READ );
buf = ipc_mkvers( &buflen );
if( 0 > queuemsg( client, buf, buflen ) )
{
free( buf );
return;
}
free( buf );
}
}
void
noop( struct bufferevent * ev UNUSED, void * arg UNUSED )
{
/* libevent prior to 1.2 couldn't handle a NULL write callback */
}
void
byebye( struct bufferevent * ev, short what, void * arg UNUSED )
{
struct client * client, key;
if( !( EVBUFFER_EOF & what ) )
{
if( EVBUFFER_TIMEOUT & what )
{
errmsg( "client connection timed out" );
}
else if( EVBUFFER_READ & what )
{
errmsg( "read error on client connection" );
}
else if( EVBUFFER_WRITE & what )
{
errmsg( "write error on client connection" );
}
else if( EVBUFFER_ERROR & what )
{
errmsg( "error on client connection" );
}
else
{
errmsg( "unknown error on client connection: 0x%x", what );
}
}
bzero( &key, sizeof key );
key.ev = ev;
client = RB_FIND( allclients, &gl_clients, &key );
assert( NULL != client );
RB_REMOVE( allclients, &gl_clients, client );
bufferevent_free( ev );
close( client->fd );
free( client );
}
void
doread( struct bufferevent * ev, void * arg )
{
struct client * client = arg;
ssize_t res;
uint8_t * buf;
size_t len;
assert( !gl_exiting );
buf = EVBUFFER_DATA( EVBUFFER_INPUT( ev ) );
len = EVBUFFER_LENGTH( EVBUFFER_INPUT( ev ) );
if( IPC_MIN_MSG_LEN > len )
{
return;
}
res = ipc_parse( &client->ipc, buf, len, client );
if( gl_exiting )
{
return;
}
if( 0 > res )
{
switch( errno )
{
case EPERM:
errmsg( "unsupported protocol version" );
break;
case EINVAL:
errmsg( "protocol parse error" );
break;
default:
errnomsg( "parsing failed" );
break;
}
byebye( ev, EVBUFFER_ERROR, NULL );
}
else if( 0 < res )
{
evbuffer_drain( EVBUFFER_INPUT( ev ), res );
}
}
int
queuemsg( struct client * client, uint8_t * buf, size_t buflen )
{
if( NULL == buf )
{
if( EPERM != errno )
{
errnomsg( "failed to build message" );
byebye( client->ev, EVBUFFER_EOF, NULL );
}
return -1;
}
if( 0 > bufferevent_write( client->ev, buf, buflen ) )
{
errnomsg( "failed to buffer %zd bytes of data for write", buflen );
return -1;
}
return 0;
}
void
msgresp( struct client * client, int64_t tag, enum ipc_msg id )
{
uint8_t * buf;
size_t buflen;
if( 0 >= tag )
{
return;
}
buf = ipc_mkempty( &client->ipc, &buflen, id, tag );
queuemsg( client, buf, buflen );
free( buf );
}
void
defmsg( enum ipc_msg id UNUSED, benc_val_t * val UNUSED, int64_t tag,
void * arg )
{
struct client * client = arg;
msgresp( client, tag, IPC_MSG_NOTSUP );
}
void
noopmsg( enum ipc_msg id UNUSED, benc_val_t * val UNUSED, int64_t tag,
void * arg )
{
struct client * client = arg;
msgresp( client, tag, IPC_MSG_OK );
}
void
addmsg1( enum ipc_msg id UNUSED, benc_val_t * val, int64_t tag, void * arg )
{
struct client * client = arg;
benc_val_t pk, * added, * file;
int ii, tor;
size_t buflen;
uint8_t * buf;
tr_info_t * inf;
if( NULL == val || TYPE_LIST != val->type )
{
msgresp( client, tag, IPC_MSG_NOTSUP );
return;
}
added = ipc_initval( &client->ipc, IPC_MSG_INFO, tag, &pk, TYPE_LIST );
if( NULL == added )
{
errnomsg( "failed to build message" );
byebye( client->ev, EVBUFFER_EOF, NULL );
return;
}
for( ii = 0; ii < val->val.l.count; ii++ )
{
file = &val->val.l.vals[ii];
if( TYPE_STR != file->type )
{
continue;
}
/* XXX need to somehow inform client of skipped or failed files */
tor = torrent_add_file( file->val.s.s, NULL, -1 );
if( TORRENT_ID_VALID( tor ) )
{
inf = torrent_info( tor );
if( 0 > ipc_addinfo( added, tor, inf, 0 ) )
{
errnomsg( "failed to build message" );
tr_bencFree( &pk );
byebye( client->ev, EVBUFFER_EOF, NULL );
return;
}
}
}
buf = ipc_mkval( &pk, &buflen );
tr_bencFree( &pk );
queuemsg( client, buf, buflen );
free( buf );
}
void
addmsg2( enum ipc_msg id UNUSED, benc_val_t * dict, int64_t tag, void * arg )
{
struct client * client = arg;
benc_val_t * val, pk;
int tor, start;
size_t buflen;
uint8_t * buf;
const char * dir;
tr_info_t * inf;
if( NULL == dict || TYPE_DICT != dict->type )
{
msgresp( client, tag, IPC_MSG_NOTSUP );
return;
}
val = tr_bencDictFind( dict, "directory" );
dir = ( NULL == val || TYPE_STR != val->type ? NULL : val->val.s.s );
val = tr_bencDictFind( dict, "autostart" );
start = ( NULL == val || TYPE_INT != val->type ? -1 :
( val->val.i ? 1 : 0 ) );
val = tr_bencDictFind( dict, "data" );
if( NULL != val && TYPE_STR == val->type )
{
/* XXX detect duplicates and return a message indicating so */
tor = torrent_add_data( ( uint8_t * )val->val.s.s, val->val.s.i,
dir, start );
}
else
{
val = tr_bencDictFind( dict, "file" );
if( NULL == val || TYPE_STR != val->type )
{
msgresp( client, tag, IPC_MSG_NOTSUP );
return;
}
/* XXX detect duplicates and return a message indicating so */
tor = torrent_add_file( val->val.s.s, dir, start );
}
if( TORRENT_ID_VALID( tor ) )
{
val = ipc_initval( &client->ipc, IPC_MSG_INFO, tag, &pk, TYPE_LIST );
if( NULL == val )
{
errnomsg( "failed to build message" );
byebye( client->ev, EVBUFFER_EOF, NULL );
return;
}
inf = torrent_info( tor );
if( 0 > ipc_addinfo( val, tor, inf, 0 ) )
{
errnomsg( "failed to build message" );
tr_bencFree( &pk );
byebye( client->ev, EVBUFFER_EOF, NULL );
return;
}
buf = ipc_mkval( &pk, &buflen );
tr_bencFree( &pk );
queuemsg( client, buf, buflen );
free( buf );
}
else
{
msgresp( client, tag, IPC_MSG_FAIL );
}
}
void
quitmsg( enum ipc_msg id UNUSED, benc_val_t * val UNUSED, int64_t tag UNUSED,
void * arg UNUSED )
{
struct client * ii, * next;
torrent_exit( 0 );
gl_exiting = 1;
for( ii = RB_MIN( allclients, &gl_clients ); NULL != ii; ii = next )
{
next = RB_NEXT( allclients, &gl_clients, ii );
byebye( ii->ev, EVBUFFER_EOF, NULL );
}
}
void
intmsg( enum ipc_msg id, benc_val_t * val, int64_t tag, void * arg )
{
struct client * client = arg;
int num;
if( NULL == val || TYPE_INT != val->type )
{
msgresp( client, tag, IPC_MSG_NOTSUP );
return;
}
num = MAX( INT_MIN, MIN( INT_MAX, val->val.i ) );
switch( id )
{
case IPC_MSG_AUTOMAP:
torrent_enable_port_mapping( num ? 1 : 0 );
break;
case IPC_MSG_AUTOSTART:
torrent_set_autostart( num ? 1 : 0 );
break;
case IPC_MSG_DOWNLIMIT:
torrent_set_downlimit( num );
break;
case IPC_MSG_PEX:
torrent_set_pex( num ? 1 : 0 );
break;
case IPC_MSG_PORT:
torrent_set_port( num );
break;
case IPC_MSG_UPLIMIT:
torrent_set_uplimit( num );
break;
default:
assert( 0 );
return;
}
msgresp( client, tag, IPC_MSG_OK );
}
void
strmsg( enum ipc_msg id, benc_val_t * val, int64_t tag, void * arg )
{
struct client * client = arg;
if( NULL == val || TYPE_STR != val->type )
{
msgresp( client, tag, IPC_MSG_NOTSUP );
return;
}
switch( id )
{
case IPC_MSG_DIR:
torrent_set_directory( val->val.s.s );
break;
default:
assert( 0 );
return;
}
msgresp( client, tag, IPC_MSG_OK );
}
void
infomsg( enum ipc_msg id, benc_val_t * val, int64_t tag, void * arg )
{
struct client * client = arg;
uint8_t * buf;
size_t buflen;
benc_val_t pk, * pkinf, * typelist, * idlist, * idval;
int all, types, ii, tor;
void * iter;
enum ipc_msg respid;
int ( * addfunc )( benc_val_t *, int, int );
all = 0;
switch( id )
{
case IPC_MSG_GETINFOALL:
all = 1;
/* FALLTHROUGH; */
case IPC_MSG_GETINFO:
respid = IPC_MSG_INFO;
addfunc = addinfo;
break;
case IPC_MSG_GETSTATALL:
all = 1;
/* FALLTHROUGH */
case IPC_MSG_GETSTAT:
respid = IPC_MSG_STAT;
addfunc = addstat;
break;
default:
assert( 0 );
return;
}
/* initialize packet */
pkinf = ipc_initval( &client->ipc, respid, tag, &pk, TYPE_LIST );
if( NULL == pkinf )
{
errnomsg( "failed to build message" );
byebye( client->ev, EVBUFFER_EOF, NULL );
return;
}
/* add info/status for all torrents */
if( all )
{
if( NULL == val || TYPE_LIST != val->type )
{
msgresp( client, tag, IPC_MSG_NOTSUP );
tr_bencFree( &pk );
return;
}
types = ipc_infotypes( respid, val );
iter = NULL;
while( NULL != ( iter = torrent_iter( iter, &tor ) ) )
{
if( 0 > addfunc( pkinf, tor, types ) )
{
errnomsg( "failed to build message" );
tr_bencFree( &pk );
byebye( client->ev, EVBUFFER_EOF, NULL );
return;
}
}
}
/* add info/status for the requested IDs */
else
{
if( NULL == val || TYPE_DICT != val->type )
{
msgresp( client, tag, IPC_MSG_NOTSUP );
tr_bencFree( &pk );
return;
}
typelist = tr_bencDictFind( val, "type" );
idlist = tr_bencDictFind( val, "id" );
if( NULL == typelist || TYPE_LIST != typelist->type ||
NULL == idlist || TYPE_LIST != idlist->type )
{
msgresp( client, tag, IPC_MSG_NOTSUP );
tr_bencFree( &pk );
return;
}
types = ipc_infotypes( respid, typelist );
for( ii = 0; idlist->val.l.count > ii; ii++ )
{
idval = &idlist->val.l.vals[ii];
if( TYPE_INT != idval->type || !TORRENT_ID_VALID( idval->val.i ) )
{
continue;
}
tor = idval->val.i;
if( 0 > addfunc( pkinf, idval->val.i, types ) )
{
errnomsg( "failed to build message" );
tr_bencFree( &pk );
byebye( client->ev, EVBUFFER_EOF, NULL );
return;
}
}
}
/* generate packet data and send it */
buf = ipc_mkval( &pk, &buflen );
tr_bencFree( &pk );
queuemsg( client, buf, buflen );
free( buf );
}
int
addinfo( benc_val_t * list, int id, int types )
{
tr_info_t * inf;
inf = torrent_info( id );
if( NULL == inf )
{
return 0;
}
return ipc_addinfo( list, id, inf, types );
}
int
addstat( benc_val_t * list, int id, int types )
{
tr_stat_t * st;
tr_info_t * inf;
st = torrent_stat( id );
if( NULL == st )
{
return 0;
}
inf = torrent_info( id );
assert( NULL != inf );
return ipc_addstat( list, id, inf, st, types );
}
void
tormsg( enum ipc_msg id, benc_val_t * val, int64_t tag, void * arg )
{
struct client * client = arg;
benc_val_t * idval;
int ii, all;
void * iter;
void ( * func )( int );
all = 0;
switch( id )
{
case IPC_MSG_REMOVEALL:
all = 1;
/* FALLTHROUGH */
case IPC_MSG_REMOVE:
func = torrent_remove;
break;
case IPC_MSG_STARTALL:
all = 1;
/* FALLTHROUGH */
case IPC_MSG_START:
func = torrent_start;
break;
case IPC_MSG_STOPALL:
all = 1;
/* FALLTHROUGH */
case IPC_MSG_STOP:
func = torrent_stop;
break;
default:
assert( 0 );
return;
}
/* remove/start/stop all torrents */
if( all )
{
iter = NULL;
while( NULL != ( iter = torrent_iter( iter, &ii ) ) )
{
func( ii );
}
}
/* remove/start/stop requested list of torrents */
else
{
if( NULL == val || TYPE_LIST != val->type )
{
msgresp( client, tag, IPC_MSG_NOTSUP );
return;
}
for( ii = 0; val->val.l.count > ii; ii++ )
{
idval = &val->val.l.vals[ii];
if( TYPE_INT != idval->type || !TORRENT_ID_VALID( idval->val.i ) )
{
continue;
}
func( idval->val.i );
}
}
msgresp( client, tag, IPC_MSG_OK );
}
void
lookmsg( enum ipc_msg id UNUSED, benc_val_t * val, int64_t tag, void * arg )
{
struct client * client = arg;
uint8_t * buf;
size_t buflen;
int ii;
benc_val_t * hash, pk, * pkinf;
int64_t found;
tr_info_t * inf;
if( NULL == val || TYPE_LIST != val->type )
{
msgresp( client, tag, IPC_MSG_NOTSUP );
return;
}
pkinf = ipc_initval( &client->ipc, IPC_MSG_INFO, tag, &pk, TYPE_LIST );
if( NULL == pkinf )
{
errnomsg( "failed to build message" );
byebye( client->ev, EVBUFFER_EOF, NULL );
return;
}
for( ii = 0; val->val.l.count > ii; ii++ )
{
hash = &val->val.l.vals[ii];
if( NULL == hash || TYPE_STR != hash->type ||
SHA_DIGEST_LENGTH * 2 != hash->val.s.i )
{
tr_bencFree( &pk );
msgresp( client, tag, IPC_MSG_NOTSUP );
return;
}
found = torrent_lookup( ( uint8_t * )hash->val.s.s );
if( !TORRENT_ID_VALID( found ) )
{
continue;
}
inf = torrent_info( found );
assert( NULL != inf );
if( 0 > ipc_addinfo( pkinf, found, inf, IPC_INF_HASH ) )
{
errnomsg( "failed to build message" );
tr_bencFree( &pk );
byebye( client->ev, EVBUFFER_EOF, NULL );
return;
}
}
buf = ipc_mkval( &pk, &buflen );
tr_bencFree( &pk );
queuemsg( client, buf, buflen );
free( buf );
}
void
prefmsg( enum ipc_msg id, benc_val_t * val UNUSED, int64_t tag, void * arg )
{
struct client * client = arg;
uint8_t * buf;
size_t buflen;
switch( id )
{
case IPC_MSG_GETAUTOMAP:
buf = ipc_mkint( &client->ipc, &buflen, IPC_MSG_AUTOMAP, tag,
torrent_get_port_mapping() );
break;
case IPC_MSG_GETAUTOSTART:
buf = ipc_mkint( &client->ipc, &buflen, IPC_MSG_AUTOSTART, tag,
torrent_get_autostart() );
break;
case IPC_MSG_GETDIR:
buf = ipc_mkstr( &client->ipc, &buflen, IPC_MSG_DIR, tag,
torrent_get_directory() );
break;
case IPC_MSG_GETDOWNLIMIT:
buf = ipc_mkint( &client->ipc, &buflen, IPC_MSG_DOWNLIMIT, tag,
torrent_get_downlimit() );
break;
case IPC_MSG_GETPEX:
buf = ipc_mkint( &client->ipc, &buflen, IPC_MSG_PEX, tag,
torrent_get_pex() );
break;
case IPC_MSG_GETPORT:
buf = ipc_mkint( &client->ipc, &buflen, IPC_MSG_PORT, tag,
torrent_get_port() );
break;
case IPC_MSG_GETUPLIMIT:
buf = ipc_mkint( &client->ipc, &buflen, IPC_MSG_UPLIMIT, tag,
torrent_get_uplimit() );
break;
default:
assert( 0 );
return;
}
queuemsg( client, buf, buflen );
free( buf );
}
void
supmsg( enum ipc_msg id UNUSED, benc_val_t * val, int64_t tag, void * arg )
{
struct client * client = arg;
uint8_t * buf;
size_t buflen;
int ii;
benc_val_t pk, *pkval, * name;
enum ipc_msg found;
if( NULL == val || TYPE_LIST != val->type )
{
msgresp( client, tag, IPC_MSG_NOTSUP );
return;
}
pkval = ipc_initval( &client->ipc, IPC_MSG_SUP, tag, &pk, TYPE_LIST );
if( NULL == pkval )
{
errnomsg( "failed to build message" );
byebye( client->ev, EVBUFFER_EOF, NULL );
return;
}
/* XXX look at other initval to make sure we free pk */
if( tr_bencListReserve( pkval, val->val.l.count ) )
{
errnomsg( "failed to build message" );
tr_bencFree( &pk );
byebye( client->ev, EVBUFFER_EOF, NULL );
return;
}
for( ii = 0; val->val.l.count > ii; ii++ )
{
name = &val->val.l.vals[ii];
if( NULL == name || TYPE_STR != name->type )
{
tr_bencFree( &pk );
msgresp( client, tag, IPC_MSG_NOTSUP );
return;
}
found = ipc_msgid( &client->ipc, name->val.s.s );
if( IPC__MSG_COUNT == found )
{
continue;
}
tr_bencInitStr( tr_bencListAdd( pkval ),
name->val.s.s, name->val.s.i, 1 );
}
buf = ipc_mkval( &pk, &buflen );
tr_bencFree( &pk );
queuemsg( client, buf, buflen );
free( buf );
}

33
daemon/server.h Normal file
View File

@@ -0,0 +1,33 @@
/******************************************************************************
* $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.
*****************************************************************************/
#ifndef TR_DAEMON_SERVER_H
#define TR_DAEMON_SERVER_H
struct event_base;
int server_init( struct event_base * );
int server_listen( int );
#endif /* TR_DAEMON_SERVER_H */

968
daemon/torrents.c Normal file
View File

@@ -0,0 +1,968 @@
/******************************************************************************
* $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/param.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/uio.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <event.h>
#include <fcntl.h>
#include <time.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "bsdtree.h"
#include "bencode.h"
#include "errors.h"
#include "misc.h"
#include "torrents.h"
#include "transmission.h"
#include "trcompat.h"
#define EXIT_TIMEOUT 10 /* how many seconds to wait on exit */
#define TIMER_SECS 1 /* timer interval seconds */
#define TIMER_USECS 0 /* timer interval microseconds */
struct tor
{
int id;
int deleting;
uint8_t hash[SHA_DIGEST_LENGTH];
tr_torrent_t * tor;
int pexset;
int pex;
RB_ENTRY( tor ) idlinks;
RB_ENTRY( tor ) hashlinks;
};
RB_HEAD( tortree, tor );
RB_HEAD( hashtree, tor );
static struct tor * opentor ( const char *, const char *, uint8_t *, size_t,
const char * );
static void closetor ( struct tor *, int );
static void starttimer ( int );
static void timerfunc ( int, short, void * );
static int loadstate ( void );
static int savestate ( void );
static int toridcmp ( struct tor *, struct tor * );
static int torhashcmp ( struct tor *, struct tor * );
static struct tor * idlookup ( int, int );
static struct tor * hashlookup ( const uint8_t *, int );
static struct tor * iterate ( struct tor * );
static struct event_base * gl_base = NULL;
static tr_handle_t * gl_handle = NULL;
static struct tortree gl_tree = RB_INITIALIZER( &gl_tree );
static struct hashtree gl_hashes = RB_INITIALIZER( &gl_hashes );
static int gl_lastid = 0;
static struct event gl_event;
static time_t gl_exiting = 0;
static int gl_exitval = 0;
static char gl_state[MAXPATHLEN];
static char gl_newstate[MAXPATHLEN];
static int gl_autostart = 1;
static int gl_pex = 1;
static int gl_port = TR_DEFAULT_PORT;
static int gl_mapping = 0;
static int gl_uplimit = -1;
static int gl_downlimit = -1;
static char gl_dir[MAXPATHLEN];
RB_GENERATE_STATIC( tortree, tor, idlinks, toridcmp )
RB_GENERATE_STATIC( hashtree, tor, hashlinks, torhashcmp )
INTCMP_FUNC( toridcmp, tor, id )
void
torrent_init( struct event_base * base )
{
assert( NULL == gl_handle && NULL == gl_base );
gl_base = base;
gl_handle = tr_init( "daemon" );
confpath( gl_state, sizeof gl_state, CONF_FILE_STATE, 0 );
strlcpy( gl_newstate, gl_state, sizeof gl_state );
strlcat( gl_newstate, ".new", sizeof gl_state );
absolutify( gl_dir, sizeof gl_dir, "." );
loadstate();
}
int
torrent_add_file( const char * path, const char * dir, int autostart )
{
struct tor * tor;
assert( NULL != gl_handle );
assert( !gl_exiting );
tor = opentor( path, NULL, NULL, 0, dir );
if( NULL == tor )
{
return -1;
}
if( 0 > autostart )
{
autostart = gl_autostart;
}
if( autostart )
{
tr_torrentStart( tor->tor );
}
savestate();
return tor->id;
}
int
torrent_add_data( uint8_t * data, size_t size, const char * dir, int autostart )
{
struct tor * tor;
assert( NULL != gl_handle );
assert( !gl_exiting );
tor = opentor( NULL, NULL, data, size, dir );
if( NULL == tor )
{
return -1;
}
if( 0 > autostart )
{
autostart = gl_autostart;
}
if( autostart )
{
tr_torrentStart( tor->tor );
}
savestate();
return tor->id;
}
void
torrent_start( int id )
{
struct tor * tor;
tr_stat_t * st;
assert( NULL != gl_handle );
assert( !gl_exiting );
tor = idlookup( id, 0 );
if( NULL == tor )
{
return;
}
st = tr_torrentStat( tor->tor );
if( TR_STATUS_INACTIVE & st->status )
{
tr_torrentStart( tor->tor );
savestate();
}
}
void
torrent_stop( int id )
{
struct tor * tor;
tr_stat_t * st;
assert( NULL != gl_handle );
assert( !gl_exiting );
tor = idlookup( id, 0 );
if( NULL == tor )
{
return;
}
st = tr_torrentStat( tor->tor );
if( TR_STATUS_ACTIVE & st->status )
{
tr_torrentStop( tor->tor );
savestate();
}
}
void
torrent_remove( int id )
{
struct tor * tor;
assert( NULL != gl_handle );
assert( !gl_exiting );
tor = idlookup( id, 0 );
if( NULL == tor )
{
return;
}
closetor( tor, 1 );
savestate();
}
tr_info_t *
torrent_info( int id )
{
struct tor * tor;
assert( NULL != gl_handle );
assert( !gl_exiting );
tor = idlookup( id, 0 );
if( NULL == tor )
{
return NULL;
}
return tr_torrentInfo( tor->tor );
}
tr_stat_t *
torrent_stat( int id )
{
struct tor * tor;
assert( NULL != gl_handle );
assert( !gl_exiting );
tor = idlookup( id, 0 );
if( NULL == tor )
{
return NULL;
}
return tr_torrentStat( tor->tor );
}
int
torrent_lookup( const uint8_t * hashstr )
{
uint8_t hash[SHA_DIGEST_LENGTH];
size_t ii;
struct tor * tor;
char buf[3];
assert( NULL != gl_handle );
assert( !gl_exiting );
bzero( buf, sizeof buf );
for( ii = 0; sizeof( hash ) > ii; ii++ )
{
if( !isxdigit( hashstr[2*ii] ) || !isxdigit( hashstr[1+2*ii] ) )
{
return -1;
}
memcpy( buf, &hashstr[2*ii], 2 );
hash[ii] = strtol( buf, NULL, 16 );
}
tor = hashlookup( hash, 0 );
if( NULL == tor )
{
return -1;
}
return tor->id;
}
void *
torrent_iter( void * iter, int * id )
{
struct tor * tor = iter;
assert( NULL != gl_handle );
assert( !gl_exiting );
tor = iterate( tor );
if( NULL != tor )
{
*id = tor->id;
}
return tor;
}
void
torrent_exit( int exitval )
{
struct tor * tor;
assert( NULL != gl_handle );
assert( !gl_exiting );
gl_exiting = time( NULL );
gl_exitval = exitval;
RB_FOREACH( tor, tortree, &gl_tree )
{
closetor( tor, 0 );
}
tr_natTraversalEnable( gl_handle, 0 );
starttimer( 1 );
}
void
torrent_set_autostart( int autostart )
{
assert( NULL != gl_handle );
assert( !gl_exiting );
gl_autostart = autostart;
savestate();
}
int
torrent_get_autostart( void )
{
return gl_autostart;
}
void
torrent_set_port( int port )
{
assert( NULL != gl_handle );
assert( !gl_exiting );
if( 0 < port && 0xffff > port )
{
gl_port = port;
tr_setBindPort( gl_handle, port );
savestate();
}
}
int
torrent_get_port( void )
{
return gl_port;
}
void
torrent_set_pex( int pex )
{
struct tor * tor;
assert( NULL != gl_handle );
assert( !gl_exiting );
if( pex == gl_pex )
{
return;
}
gl_pex = pex;
for( tor = iterate( NULL ); NULL != tor; tor = iterate( tor ) )
{
if( tor->pexset )
{
continue;
}
tr_torrentDisablePex( tor->tor, !gl_pex );
}
savestate();
}
int
torrent_get_pex( void )
{
return gl_pex;
}
void
torrent_enable_port_mapping( int automap )
{
assert( NULL != gl_handle );
assert( !gl_exiting );
gl_mapping = ( automap ? 1 : 0 );
tr_natTraversalEnable( gl_handle, gl_mapping );
savestate();
}
int
torrent_get_port_mapping( void )
{
return gl_mapping;
}
void
torrent_set_uplimit( int uplimit )
{
assert( NULL != gl_handle );
assert( !gl_exiting );
gl_uplimit = uplimit;
tr_setGlobalUploadLimit( gl_handle, uplimit );
savestate();
}
int
torrent_get_uplimit( void )
{
return gl_uplimit;
}
void
torrent_set_downlimit( int downlimit )
{
assert( NULL != gl_handle );
assert( !gl_exiting );
gl_downlimit = downlimit;
tr_setGlobalDownloadLimit( gl_handle, downlimit );
savestate();
}
int
torrent_get_downlimit( void )
{
return gl_downlimit;
}
void
torrent_set_directory( const char * path )
{
assert( NULL != gl_handle );
assert( !gl_exiting );
absolutify( gl_dir, sizeof gl_dir, path );
savestate();
}
const char *
torrent_get_directory( void )
{
return gl_dir;
}
struct tor *
opentor( const char * path, const char * hash, uint8_t * data, size_t size,
const char * dir )
{
struct tor * tor, * found;
int errcode;
tr_info_t * inf;
assert( ( NULL != path && NULL == hash && NULL == data ) ||
( NULL == path && NULL != hash && NULL == data ) ||
( NULL == path && NULL == hash && NULL != data ) );
/* XXX should probably wrap around back to 1 and avoid duplicates */
if( INT_MAX == gl_lastid )
{
errmsg( "Congratulations, you're the %ith torrent! Your prize the "
"inability to load any more torrents, enjoy!", INT_MAX );
return NULL;
}
tor = calloc( 1, sizeof *tor );
if( NULL == tor )
{
mallocmsg( sizeof *tor );
return NULL;
}
if( NULL != path )
{
tor->tor = tr_torrentInit( gl_handle, path, tor->hash,
TR_FLAG_SAVE, &errcode );
}
else if( NULL != hash )
{
tor->tor = tr_torrentInitSaved( gl_handle, hash, 0, &errcode );
}
else
{
tor->tor = tr_torrentInitData( gl_handle, data, size, tor->hash,
TR_FLAG_SAVE, &errcode );
}
if( NULL == tor->tor )
{
found = NULL;
switch( errcode )
{
case TR_EINVALID:
if( NULL == path )
{
errmsg( "invalid torrent file" );
}
else
{
errmsg( "invalid torrent file: %s", path );
}
break;
case TR_EUNSUPPORTED:
if( NULL == path )
{
errmsg( "unsupported torrent file" );
}
else
{
errmsg( "unsupported torrent file: %s", path );
}
break;
case TR_EDUPLICATE:
/* XXX not yet
found = hashlookup( tor->hash, 1 );
assert( NULL != found );
found->deleting = 0;
*/
errmsg( "XXX loaded duplicate torrent" );
break;
default:
if( NULL == path )
{
errmsg( "torrent file failed to load" );
}
else
{
errmsg( "torrent file failed to load: %s", path );
}
break;
}
free( tor );
return found;
}
gl_lastid++;
tor->id = gl_lastid;
tor->deleting = 0;
assert( sizeof( inf->hash ) == sizeof( tor->hash ) );
inf = tr_torrentInfo( tor->tor );
memcpy( tor->hash, inf->hash, sizeof tor->hash );
tr_torrentSetFolder( tor->tor, ( NULL == dir ? gl_dir : dir ) );
if( TR_FLAG_PRIVATE & inf->flags )
{
tor->pexset = 1;
tor->pex = 0;
}
else
{
tr_torrentDisablePex( tor->tor, !gl_pex );
}
found = RB_INSERT( tortree, &gl_tree, tor );
assert( NULL == found );
found = RB_INSERT( hashtree, &gl_hashes, tor );
assert( NULL == found );
return tor;
}
void
closetor( struct tor * tor, int calltimer )
{
tr_stat_t * st;
if( NULL == tor || tor->deleting )
{
return;
}
tor->deleting = 1;
st = tr_torrentStat( tor->tor );
if( TR_STATUS_ACTIVE & st->status )
{
tr_torrentStop( tor->tor );
}
starttimer( calltimer );
}
void
starttimer( int callnow )
{
if( !evtimer_initialized( &gl_event ) )
{
evtimer_set( &gl_event, timerfunc, NULL );
/* XXX event_base_set( gl_base, &gl_event ); */
}
if( callnow )
{
timerfunc( -1, EV_TIMEOUT, NULL );
}
}
void
timerfunc( int fd UNUSED, short event UNUSED, void * arg UNUSED )
{
struct tor * tor, * next;
tr_handle_status_t * hs;
tr_stat_t * st;
int stillmore;
struct timeval tv;
stillmore = 0;
for( tor = RB_MIN( tortree, &gl_tree ); NULL != tor; tor = next )
{
next = RB_NEXT( tortree, &gl_tree, tor );
if( !tor->deleting )
{
continue;
}
st = tr_torrentStat( tor->tor );
if( TR_STATUS_PAUSE & st->status )
{
tr_torrentClose( gl_handle, tor->tor );
RB_REMOVE( tortree, &gl_tree, tor );
RB_REMOVE( hashtree, &gl_hashes, tor );
free( tor );
}
else
{
stillmore = 1;
}
}
if( gl_exiting )
{
if( !stillmore )
{
hs = tr_handleStatus( gl_handle );
if( TR_NAT_TRAVERSAL_DISABLED != hs->natTraversalStatus )
{
stillmore = 1;
}
}
if( !stillmore || EXIT_TIMEOUT <= time( NULL ) - gl_exiting )
{
if( stillmore )
{
errmsg( "timing out trackers and/or port mapping on exit" );
}
for( tor = RB_MIN( tortree, &gl_tree ); NULL != tor; tor = next )
{
next = RB_NEXT( tortree, &gl_tree, tor );
tr_torrentClose( gl_handle, tor->tor );
RB_REMOVE( tortree, &gl_tree, tor );
RB_REMOVE( hashtree, &gl_hashes, tor );
free( tor );
}
tr_close( gl_handle );
exit( gl_exitval );
}
}
if( stillmore )
{
bzero( &tv, sizeof tv );
tv.tv_sec = TIMER_SECS;
tv.tv_usec = TIMER_USECS;
evtimer_add( &gl_event, &tv );
}
}
int
loadstate( void )
{
uint8_t * buf;
size_t len;
benc_val_t top, * num, * str, * list, * dict;
int ii;
struct tor * tor;
const char * dir;
buf = readfile( gl_state, &len );
if( NULL == buf )
{
return -1;
}
if( tr_bencLoad( buf, len, &top, NULL ) )
{
free( buf );
errmsg( "failed to load bencoded data from %s", gl_state );
return -1;
}
free( buf );
num = tr_bencDictFind( &top, "autostart" );
if( NULL != num && TYPE_INT == num->type )
{
gl_autostart = ( num->val.i ? 1 : 0 );
}
num = tr_bencDictFind( &top, "port" );
if( NULL != num && TYPE_INT == num->type &&
0 < num->val.i && 0xffff > num->val.i )
{
gl_port = num->val.i;
}
tr_setBindPort( gl_handle, gl_port );
num = tr_bencDictFind( &top, "default-pex" );
if( NULL != num && TYPE_INT == num->type )
{
gl_pex = ( num->val.i ? 1 : 0 );
}
num = tr_bencDictFind( &top, "port-mapping" );
if( NULL != num && TYPE_INT == num->type )
{
gl_mapping = ( num->val.i ? 1 : 0 );
}
tr_natTraversalEnable( gl_handle, gl_mapping );
num = tr_bencDictFind( &top, "upload-limit" );
if( NULL != num && TYPE_INT == num->type )
{
gl_uplimit = num->val.i;
}
tr_setGlobalUploadLimit( gl_handle, gl_uplimit );
num = tr_bencDictFind( &top, "download-limit" );
if( NULL != num && TYPE_INT == num->type )
{
gl_downlimit = num->val.i;
}
tr_setGlobalDownloadLimit( gl_handle, gl_downlimit );
str = tr_bencDictFind( &top, "default-directory" );
if( NULL != str && TYPE_STR == str->type )
{
strlcpy( gl_dir, str->val.s.s, sizeof gl_dir );
}
list = tr_bencDictFind( &top, "torrents" );
if( NULL == list || TYPE_LIST != list->type )
{
return 0;
}
for( ii = 0; ii < list->val.l.count; ii++ )
{
dict = &list->val.l.vals[ii];
if( TYPE_DICT != dict->type )
{
continue;
}
str = tr_bencDictFind( dict, "directory" );
dir = ( NULL != str && TYPE_STR == str->type ? str->val.s.s : NULL );
str = tr_bencDictFind( dict, "hash" );
if( NULL == str || TYPE_STR != str->type ||
2 * SHA_DIGEST_LENGTH != str->val.s.i )
{
continue;
}
tor = opentor( NULL, str->val.s.s, NULL, 0, dir );
if( NULL == tor )
{
continue;
}
num = tr_bencDictFind( dict, "pex" );
if( NULL != num && TYPE_INT == num->type )
{
tor->pexset = 1;
tor->pex = ( num->val.i ? 1 : 0 );
}
tr_torrentDisablePex( tor->tor, !( tor->pexset ? tor->pex : gl_pex ) );
num = tr_bencDictFind( dict, "paused" );
if( NULL != num && TYPE_INT == num->type && !num->val.i )
{
tr_torrentStart( tor->tor );
}
}
return 0;
}
int
savestate( void )
{
benc_val_t top, * list, * tor;
struct tor * ii;
tr_info_t * inf;
tr_stat_t * st;
uint8_t * buf;
int len, pexset;
tr_bencInit( &top, TYPE_DICT );
if( tr_bencDictReserve( &top, 8 ) )
{
nomem:
tr_bencFree( &top );
errmsg( "failed to save state: failed to allocate memory" );
return -1;
}
tr_bencInitInt( tr_bencDictAdd( &top, "autostart" ), gl_autostart );
tr_bencInitInt( tr_bencDictAdd( &top, "port" ), gl_port );
tr_bencInitInt( tr_bencDictAdd( &top, "default-pex" ), gl_pex );
tr_bencInitInt( tr_bencDictAdd( &top, "port-mapping" ), gl_mapping );
tr_bencInitInt( tr_bencDictAdd( &top, "upload-limit" ), gl_uplimit );
tr_bencInitInt( tr_bencDictAdd( &top, "download-limit" ), gl_downlimit );
tr_bencInitStr( tr_bencDictAdd( &top, "default-directory" ),
gl_dir, -1, 1 );
list = tr_bencDictAdd( &top, "torrents" );
tr_bencInit( list, TYPE_LIST );
len = 0;
RB_FOREACH( ii, tortree, &gl_tree )
{
if( !ii->deleting )
{
len++;
}
}
if( tr_bencListReserve( list, len ) )
{
goto nomem;
}
RB_FOREACH( ii, tortree, &gl_tree )
{
if( ii->deleting )
{
continue;
}
tor = tr_bencListAdd( list );
assert( NULL != tor );
tr_bencInit( tor, TYPE_DICT );
inf = tr_torrentInfo( ii->tor );
st = tr_torrentStat( ii->tor );
pexset = ( ii->pexset && !( TR_FLAG_PRIVATE & inf->flags ) );
if( tr_bencDictReserve( tor, ( pexset ? 4 : 3 ) ) )
{
goto nomem;
}
tr_bencInitStr( tr_bencDictAdd( tor, "hash" ),
inf->hashString, 2 * SHA_DIGEST_LENGTH, 1 );
tr_bencInitInt( tr_bencDictAdd( tor, "paused" ),
( TR_STATUS_INACTIVE & st->status ? 1 : 0 ) );
tr_bencInitStr( tr_bencDictAdd( tor, "directory" ),
tr_torrentGetFolder( ii->tor ), -1, 1 );
if( pexset )
{
tr_bencInitInt( tr_bencDictAdd( tor, "pex" ), ii->pex );
}
}
buf = ( uint8_t * )tr_bencSaveMalloc( &top, &len );
SAFEBENCFREE( &top );
if( NULL == buf )
{
errnomsg( "failed to save state: bencoding failed" );
return -1;
}
if( 0 > writefile( gl_newstate, buf, len ) )
{
free( buf );
return -1;
}
free( buf );
if( 0 > rename( gl_newstate, gl_state ) )
{
errnomsg( "failed to save state: failed to rename %s to %s",
gl_newstate, CONF_FILE_STATE );
return -1;
}
return 0;
}
int
torhashcmp( struct tor * left, struct tor * right )
{
return memcmp( left->hash, right->hash, sizeof left->hash );
}
struct tor *
idlookup( int id, int wantdel )
{
struct tor key, * found;
bzero( &key, sizeof key );
key.id = id;
found = RB_FIND( tortree, &gl_tree, &key );
if( NULL != found && !wantdel && found->deleting )
{
found = NULL;
}
return found;
}
struct tor *
hashlookup( const uint8_t * hash, int wantdel )
{
struct tor key, * found;
bzero( &key, sizeof key );
memcpy( key.hash, hash, sizeof key.hash );
found = RB_FIND( hashtree, &gl_hashes, &key );
if( NULL != found && !wantdel && found->deleting )
{
found = NULL;
}
return found;
}
struct tor *
iterate( struct tor * tor )
{
assert( NULL != gl_handle );
assert( !gl_exiting );
if( NULL == tor )
{
tor = RB_MIN( tortree, &gl_tree );
}
else
{
tor = RB_NEXT( tortree, &gl_tree, tor );
}
while( NULL != tor && tor->deleting )
{
tor = RB_NEXT( tortree, &gl_tree, tor );
}
return tor;
}

62
daemon/torrents.h Normal file
View File

@@ -0,0 +1,62 @@
/******************************************************************************
* $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.
*****************************************************************************/
#ifndef TR_DAEMON_TORRENTS_H
#define TR_DAEMON_TORRENTS_H
#include <limits.h>
/* typedefs suck */
#include "transmission.h"
struct event_base;
void torrent_init ( struct event_base * );
int torrent_add_file ( const char *, const char *, int );
int torrent_add_data ( uint8_t *, size_t, const char *, int );
void torrent_start ( int );
void torrent_stop ( int );
void torrent_remove ( int );
tr_info_t * torrent_info ( int );
tr_stat_t * torrent_stat ( int );
int torrent_lookup ( const uint8_t * );
void * torrent_iter ( void *, int * );
void torrent_exit ( int );
void torrent_set_autostart ( int );
int torrent_get_autostart ( void );
void torrent_set_port ( int );
int torrent_get_port ( void );
void torrent_set_pex ( int );
int torrent_get_pex ( void );
void torrent_enable_port_mapping ( int );
int torrent_get_port_mapping ( void );
void torrent_set_uplimit ( int );
int torrent_get_uplimit ( void );
void torrent_set_downlimit ( int );
int torrent_get_downlimit ( void );
void torrent_set_directory ( const char * );
const char * torrent_get_directory ( void );
#endif /* TR_DAEMON_TORRENTS_H */

View File

@@ -0,0 +1,64 @@
.\" $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.
.Dd March 30, 2007
.Dt TRANSMISSION-DAEMON 1
.Os
.Sh NAME
.Nm transmission-daemon
.Nd a bittorrent client
.Sh SYNOPSIS
.Nm transmission-daemon
.Fl h
.Nm
.Op Fl f
.Sh DESCRIPTION
The
.Nm
program cures baldness, obesity and gout.
.Pp
The options are as follows:
.Bl -tag -width Ds
.It Fl f Fl -foreground
Run in the foreground and print errors to stderr instead of forking
and logging errors with syslog.
.It Fl h Fl -help
Print command-line option descriptions.
.El
.Sh FILES
.Pa ~/.transmission
.Sh AUTHORS
.An -nosplit
The
.Nm
program was written by
.An Josh Elsasser Aq josh@elsasser.org ,
.An Eric Petit Aq titer@m0k.org ,
and
.An Mitchell Livingston Aq livings124@gmail.com .
.Sh SEE ALSO
.Xr transmissioncli 1 ,
.Xr transmission-gtk 1 ,
.Xr transmission-proxy 1 ,
.Xr transmission-remote 1
.Pp
http://transmission.m0k.org

View File

@@ -0,0 +1,65 @@
.\" $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.
.Dd March 30, 2007
.Dt TRANSMISSION-PROXY 1
.Os
.Sh NAME
.Nm transmission-proxy
.Nd a bittorrent client
.Sh SYNOPSIS
.Nm transmission-proxy
.Fl h
.Nm
.Op Fl t
.Sh DESCRIPTION
The
.Nm
program comprises 37% of the diet of the average swede.
.Pp
The options are as follows:
.Bl -tag -width Ds
.It Fl t Fl -type Ar daemon | gtk
Connect to either
.Xr transmission-daemon 1
or
.Xr transmission-gtk 1 .
.It Fl h Fl -help
Print command-line option descriptions.
.El
.Sh FILES
.Pa ~/.transmission
.Sh AUTHORS
.An -nosplit
The
.Nm
program was written by
.An Josh Elsasser Aq josh@elsasser.org ,
.An Eric Petit Aq titer@m0k.org ,
and
.An Mitchell Livingston Aq livings124@gmail.com .
.Sh SEE ALSO
.Xr transmissioncli 1 ,
.Xr transmission-gtk 1 ,
.Xr transmission-remote 1
.Pp
http://transmission.m0k.org

View File

@@ -0,0 +1,206 @@
.\" $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.
.Dd March 30, 2007
.Dt TRANSMISSION-REMOTE 1
.Os
.Sh NAME
.Nm transmission-remote
.Nd a remote control utility for
.Xr transmission-daemon 1
and
.Xr transmission-gtk 1
.Sh SYNOPSIS
.Bk -words
.Nm transmission-remote
.Fl h
.Nm
.Oo
.Fl t Ar daemon | Ar gtk
.Oc
.Op Fl a Ar torrent-file
.Op Fl d Ar download-rate
.Op Fl D
.Op Fl e
.Op Fl E
.Op Fl f Ar directory
.Op Fl i
.Op Fl l
.Op Fl m
.Op Fl M
.Op Fl p Ar port
.Op Fl q
.Oo
.Fl r Ar all | Ar info-hash
.Oc
.Oo
.Fl s Ar all | Ar info-hash
.Oc
.Oo
.Fl S Ar all | Ar info-hash
.Oc
.Op Fl u Ar upload-rate
.Op Fl U
.Op Ar torrent-file ...
.Nm
.Fl x
.Op Fl a Ar torrent-file
.Op Fl d Ar download-rate
.Op Fl D
.Op Fl e
.Op Fl E
.Op Fl f Ar directory
.Op Fl i
.Op Fl l
.Op Fl m
.Op Fl M
.Op Fl p Ar port
.Op Fl q
.Oo
.Fl r Ar all | Ar info-hash
.Oc
.Oo
.Fl s Ar all | Ar info-hash
.Oc
.Oo
.Fl S Ar all | Ar info-hash
.Oc
.Op Fl u Ar upload-rate
.Op Fl U
.Ar command
.Op Ar argument ...
.Ek
.Sh DESCRIPTION
The
.Nm
program is a vile scoundral lacking the most fundamental principles of
morality and decency.
.Pp
The options are as follows:
.Bl -tag -width Ds
.It Fl a Fl -add Ar torrent-file
Add the torrent metainfo file
.Ar torrent-file .
.It Fl d Fl -download-limit Ar download-rate
Set maximum download rate to
.Ar download-rate
in kilobytes per second.
.It Fl D Fl -download-unlimited
Remove the download limit.
.It Fl e
Enable peer exchange.
.It Fl E
Disable peer exchange.
.It Fl f Fl -folder Ar directory
Use
.Ar directory
as the default location for newly added torrents to download files to.
.It Fl h Fl -help
Print command-line option descriptions.
.It Fl i Fl -info
Print a listing of all torrent info hashes and names.
.It Fl l Fl -list
Print a listing of all torrent names with size and current status.
.It Fl m Fl -port-mapping
Enable automatic port mapping via NAT-PMP or UPnP IGD.
.It Fl M Fl -no-port-mapping
Disable automatic port mapping.
.It Fl p Fl -port Ar port
Attempt to bind to
.Ar port
for use as a listening port to accept incoming peer connections.
.It Fl q Fl -quit
Quit the running instance.
.It Fl r Fl -remove Ar all | info-hash
Remove all torrents, or the torrent with an info hash of
.Ar info-hash .
Neither the downloaded data nor the original torrent metainfo file
will be deleted.
.It Fl s Fl -start Ar all | info-hash
Stop all torrents from downloading or seeding, or the torrent with an
info hash of
.Ar info-hash .
.It Fl S Fl -stop Ar all | info-hash
Start all torrents downloading or seeding, or the torrent with an info
hash of
.Ar info-hash .
.It Fl t Fl -type Ar daemon | gtk
Connect to either
.Xr transmission-daemon 1
or
.Xr transmission-gtk 1 .
.It Fl u Fl -upload-limit Ar upload-rate
Set maximum upload rate to
.Ar upload-rate
in kilobytes per second.
.It Fl U Fl -upload-unlimited
Remove the upload limit.
.It Fl x Fl -proxy
Proxy the connection to
.Xr transmission-daemon 1
or
.Xr transmission-gtk 1
through
.Ar command .
.El
.Sh FILES
.Pa ~/.transmission
.Sh EXAMPLES
Show status for all torrents:
.Pp
.Dl transmission-remote -l
.Pp
Pause a torrent:
.Pp
.Dl transmission-remote -S ca6ac4bbd971d3902935dbcfc2d3ea25b428a547
.Pp
Set download and upload limits to 100 KiB/sec and 20 KiB/sec:
.Pp
.Dl transmission-remote -d 100 -u 20
.Pp
Quit the GTK+ frontend:
.Pp
.Dl transmission-remote -t gtk -q
.Pp
Add two torrents:
.Pp
.Dl transmission-remote foo.torrent bar.torrent
.Pp
Show the info hashes of all the torrents on jade:
.Pp
.Dl transmission-remote -x -i ssh jade transmission-proxy
.Sh AUTHORS
.An -nosplit
The
.Nm
program was written by
.An Josh Elsasser Aq josh@elsasser.org ,
.An Eric Petit Aq titer@m0k.org ,
and
.An Mitchell Livingston Aq livings124@gmail.com .
.Sh SEE ALSO
.Xr transmissioncli 1 ,
.Xr transmission-daemon 1 ,
.Xr transmission-gtk 1 ,
.Xr transmission-proxy 1
.Pp
http://transmission.m0k.org

View File

@@ -80,6 +80,9 @@ program was written by
and
.An Mitchell Livingston Aq livings124@gmail.com .
.Sh SEE ALSO
.Xr transmissioncli 1
.Xr transmissioncli 1 ,
.Xr transmission-daemon 1 ,
.Xr transmission-proxy 1 ,
.Xr transmission-remote 1
.Pp
http://transmission.m0k.org

View File

@@ -131,6 +131,7 @@ typedef struct tr_bitfield_s tr_bitfield_t;
typedef enum { TR_NET_OK, TR_NET_ERROR, TR_NET_WAIT } tr_tristate_t;
#include "trcompat.h"
#include "bsdqueue.h"
#include "bsdtree.h"
#include "platform.h"

View File

@@ -110,7 +110,8 @@ char * tr_getPrefsDirectory()
strcat( prefsDirectory, "/Transmission" );
#elif defined( SYS_DARWIN )
snprintf( prefsDirectory, MAX_PATH_LENGTH,
"%s/Library/Caches/Transmission", tr_getHomeDirectory() );
"%s/Library/Application Support/Transmission",
tr_getHomeDirectory() );
#elif defined(__AMIGAOS4__)
snprintf( prefsDirectory, MAX_PATH_LENGTH, "PROGDIR:.transmission" );
#else
@@ -147,8 +148,8 @@ char * tr_getCacheDirectory()
snprintf( cacheDirectory, MAX_PATH_LENGTH, "%s/Cache",
tr_getPrefsDirectory() );
#elif defined( SYS_DARWIN )
snprintf( cacheDirectory, MAX_PATH_LENGTH, "%s",
tr_getPrefsDirectory() );
snprintf( cacheDirectory, MAX_PATH_LENGTH, "%s/Caches/Transmission",
tr_getHomeDirectory() );
#else
snprintf( cacheDirectory, MAX_PATH_LENGTH, "%s/cache",
tr_getPrefsDirectory() );
@@ -180,8 +181,7 @@ char * tr_getTorrentsDirectory()
snprintf( torrentsDirectory, MAX_PATH_LENGTH, "%s/Torrents",
tr_getPrefsDirectory() );
#elif defined( SYS_DARWIN )
snprintf( torrentsDirectory, MAX_PATH_LENGTH,
"%s/Library/Application Support/Transmission/Torrents",
snprintf( torrentsDirectory, MAX_PATH_LENGTH, "%s/Torrents",
tr_getHomeDirectory() );
#else
snprintf( torrentsDirectory, MAX_PATH_LENGTH, "%s/torrents",

60
libtransmission/strlcat.c Normal file
View File

@@ -0,0 +1,60 @@
/* $Id$ */
/* $OpenBSD: strlcat.c,v 1.13 2005/08/08 08:05:37 espie Exp $ */
/*
* Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <string.h>
#ifndef HAVE_STRLCAT
/*
* Appends src to string dst of size siz (unlike strncat, siz is the
* full size of dst, not space left). At most siz-1 characters
* will be copied. Always NUL terminates (unless siz <= strlen(dst)).
* Returns strlen(src) + MIN(siz, strlen(initial dst)).
* If retval >= siz, truncation occurred.
*/
size_t
strlcat(char *dst, const char *src, size_t siz)
{
char *d = dst;
const char *s = src;
size_t n = siz;
size_t dlen;
/* Find the end of dst and adjust bytes left but don't go past end */
while (n-- != 0 && *d != '\0')
d++;
dlen = d - dst;
n = siz - dlen;
if (n == 0)
return(dlen + strlen(s));
while (*s != '\0') {
if (n != 1) {
*d++ = *s;
n--;
}
s++;
}
*d = '\0';
return(dlen + (s - src)); /* count does not include NUL */
}
#endif /* HAVE_STRLCAT */

56
libtransmission/strlcpy.c Normal file
View File

@@ -0,0 +1,56 @@
/* $Id$ */
/* $OpenBSD: strlcpy.c,v 1.11 2006/05/05 15:27:38 millert Exp $ */
/*
* Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <string.h>
#ifndef HAVE_STRLCPY
/*
* Copy src to string dst of size siz. At most siz-1 characters
* will be copied. Always NUL terminates (unless siz == 0).
* Returns strlen(src); if retval >= siz, truncation occurred.
*/
size_t
strlcpy(char *dst, const char *src, size_t siz)
{
char *d = dst;
const char *s = src;
size_t n = siz;
/* Copy as many bytes as will fit */
if (n != 0) {
while (--n != 0) {
if ((*d++ = *s++) == '\0')
break;
}
}
/* Not enough room in dst, add NUL and traverse rest of src */
if (n == 0) {
if (siz != 0)
*d = '\0'; /* NUL-terminate dst */
while (*s++)
;
}
return(s - src - 1); /* count does not include NUL */
}
#endif /* HAVE_STRLCPY */

View File

@@ -0,0 +1,37 @@
/******************************************************************************
* $Id$
*
* Copyright (c) 2005-2007 Transmission authors and contributors
*
* 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.
*****************************************************************************/
#ifndef TRCOMPAT_H
#define TRCOMPAT_H
#ifndef HAVE_STRLCPY
size_t
strlcpy(char *dst, const char *src, size_t siz);
#endif
#ifndef HAVE_STRLCAT
size_t
strlcat(char *dst, const char *src, size_t siz);
#endif
#endif /* TRCOMPAT_H */

70
mk/daemon.mk Normal file
View File

@@ -0,0 +1,70 @@
# $Id$
include ../mk/config.mk
include ../mk/common.mk
COMSRCS = errors.c ipc.c misc.c
SRVSRCS = daemon.c server.c torrents.c
CLISRCS = client.c remote.c
PXYSRCS = proxy.c
COMOBJS = $(COMSRCS:%.c=%.o)
SRVOBJS = $(SRVSRCS:%.c=%.o)
CLIOBJS = $(CLISRCS:%.c=%.o)
PXYOBJS = $(PXYSRCS:%.c=%.o)
SRCS = $(COMSRCS) $(SRVSRCS) $(CLISRCS) $(PXYSRCS)
CFLAGS += $(CFLAGS_EVENT) -I../libtransmission
LDLIBS += ../libtransmission/libtransmission.a
LDFLAGS += $(LDFLAGS_EVENT)
all: transmission-daemon transmission-remote transmission-proxy
transmission-daemon: OBJS = $(SRVOBJS) $(COMOBJS)
transmission-daemon: $(LDLIBS) $(SRVOBJS) $(COMOBJS)
$(LINK_RULE)
transmission-remote: OBJS = $(CLIOBJS) $(COMOBJS)
transmission-remote: $(LDLIBS) $(CLIOBJS) $(COMOBJS)
$(LINK_RULE)
transmission-proxy: OBJS = $(PXYOBJS) $(COMOBJS)
transmission-proxy: $(LDLIBS) $(PXYOBJS) $(COMOBJS)
$(LINK_RULE)
%.o: %.c ../mk/config.mk ../mk/common.mk ../mk/daemon.mk
$(CC_RULE)
clean:
@echo "Clean transmission-daemon"
@echo "Clean transmission-remote"
@echo "Clean transmission-proxy"
@echo "Clean $(COMOBJS) $(SRVOBJS) $(CLIOBJS) $(PXYOBJS)"
@$(RM) transmission-daemon transmission-remote
@$(RM) $(COMOBJS) $(SRVOBJS) $(CLIOBJS) $(PXYOBJS)
.depend: $(SRCS) ../mk/config.mk ../mk/common.mk ../mk/daemon.mk
$(DEP_RULE)
install: install.srv install.srv.man install.cli install.cli.man \
install.pxy install.pxy.man
install.srv: transmission-daemon
$(INSTALL_BIN_RULE)
install.srv.man: transmission-daemon.1
$(INSTALL_MAN_RULE)
install.cli: transmission-remote
$(INSTALL_BIN_RULE)
install.cli.man: transmission-remote.1
$(INSTALL_MAN_RULE)
install.pxy: transmission-proxy
$(INSTALL_BIN_RULE)
install.pxy.man: transmission-proxy.1
$(INSTALL_MAN_RULE)
-include .depend

View File

@@ -7,6 +7,9 @@ TARGETS = .cli
ifeq ($(GTK),yes)
TARGETS += .gtk
endif
ifeq ($(DAEMON),yes)
TARGETS += .daemon
endif
ifeq ($(SYSTEM),BeOS)
TARGETS += .beos
endif
@@ -25,6 +28,10 @@ all: $(TARGETS)
@echo "* Building Transmission GTK+ client"
@$(MAKE) $(MAKEARGS) -C gtk -f ../mk/gtk.mk
.daemon: .lib
@echo "* Building Transmission daemon client"
@$(MAKE) $(MAKEARGS) -C daemon -f ../mk/daemon.mk
.beos: .lib
@echo "* Building Transmission BeOS client"
@$(MAKE) $(MAKEARGS) -C beos -f ../mk/beos.mk
@@ -39,6 +46,10 @@ install: all $(foreach SUB,$(TARGETS),.install$(SUB)) .install.misc
@echo "* Installing Transmission GTK+ client"
@$(MAKE) $(MAKEARGS) -C gtk -f ../mk/gtk.mk install
.install.daemon: .daemon
@echo "* Installing Transmission daemon client"
@$(MAKE) $(MAKEARGS) -C daemon -f ../mk/daemon.mk install
.install.beos:
.install.misc:
@@ -52,6 +63,9 @@ clean:
ifeq ($(GTK),yes)
@$(MAKE) $(MAKEARGS) -C gtk -f ../mk/gtk.mk clean
endif
ifeq ($(DAEMON),yes)
@$(MAKE) $(MAKEARGS) -C daemon -f ../mk/daemon.mk clean
endif
ifeq ($(SYSTEM),BeOS)
@$(MAKE) $(MAKEARGS) -C beos -f ../mk/beos.mk clean
endif

View File

@@ -6,7 +6,7 @@ include ../mk/common.mk
SRCS = transmission.c bencode.c net.c tracker.c peer.c inout.c \
metainfo.c sha1.c utils.c fdlimit.c clients.c completion.c \
platform.c ratecontrol.c choking.c natpmp.c upnp.c http.c xml.c \
shared.c torrent.c
shared.c torrent.c strlcpy.c strlcat.c
OBJS = $(SRCS:%.c=%.o)