mirror of
https://github.com/transmission/transmission
synced 2024-12-24 16:52:39 +00:00
229d9c84dc
GObject-derived wrappers are used for tr_handle_t and tr_torrent_t. Use bencoding to store prefs and state file. Make sure to always group error messages when adding multiple torrents at once. Remove some unused code. Many miscellaneous cleanups.
432 lines
11 KiB
C
432 lines
11 KiB
C
/*
|
|
Copyright (c) 2006 Joshua Elsasser. All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions
|
|
are met:
|
|
|
|
1. Redistributions of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
2. Redistributions in binary form must reproduce the above copyright
|
|
notice, this list of conditions and the following disclaimer in the
|
|
documentation and/or other materials provided with the distribution.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS "AS IS" AND
|
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include <gtk/gtk.h>
|
|
#include <glib/gi18n.h>
|
|
|
|
#include "transmission.h"
|
|
#include "bencode.h"
|
|
|
|
#include "conf.h"
|
|
#include "io.h"
|
|
#include "ipc.h"
|
|
#include "util.h"
|
|
|
|
#define PROTOVERS 1 /* IPC protocol version */
|
|
|
|
/* int, IPC protocol version */
|
|
#define MSG_VERSION ("version")
|
|
/* list of strings, full paths to torrent files to load */
|
|
#define MSG_ADDFILES ("addfiles")
|
|
|
|
enum contype { CON_SERV, CON_ADDFILE };
|
|
|
|
struct constate_serv {
|
|
void *wind;
|
|
add_torrents_func_t addfunc;
|
|
void *cbdata;
|
|
};
|
|
|
|
struct constate_addfile {
|
|
GMainLoop *loop;
|
|
GList *files;
|
|
gboolean *succeeded;
|
|
unsigned int addid;
|
|
};
|
|
|
|
struct constate;
|
|
typedef void (*handler_func_t)(struct constate*, const char*, benc_val_t *);
|
|
struct handlerdef {char *name; handler_func_t func;};
|
|
|
|
struct constate {
|
|
GSource *source;
|
|
int fd;
|
|
const struct handlerdef *funcs;
|
|
enum contype type;
|
|
union {
|
|
struct constate_serv serv;
|
|
struct constate_addfile addfile;
|
|
} u;
|
|
};
|
|
|
|
void
|
|
ipc_socket_setup(void *parent, add_torrents_func_t addfunc, void *cbdata);
|
|
gboolean
|
|
ipc_sendfiles_blocking(GList *files);
|
|
static void
|
|
serv_bind(struct constate *con);
|
|
static void
|
|
rmsock(void);
|
|
static gboolean
|
|
client_connect(char *path, struct constate *con);
|
|
static void
|
|
srv_io_accept(GSource *source, int fd, struct sockaddr *sa, socklen_t len,
|
|
void *vdata);
|
|
static int
|
|
send_msg(struct constate *con, const char *name, benc_val_t *val);
|
|
static int
|
|
send_msg_int(struct constate *con, const char *name, int num);
|
|
static unsigned int
|
|
all_io_received(GSource *source, char *data, unsigned int len, void *vdata);
|
|
static void
|
|
destroycon(struct constate *con);
|
|
static void
|
|
all_io_closed(GSource *source, void *vdata);
|
|
static void
|
|
srv_addfile(struct constate *con, const char *name, benc_val_t *val);
|
|
static void
|
|
afc_version(struct constate *con, const char *name, benc_val_t *val);
|
|
static void
|
|
afc_io_sent(GSource *source, unsigned int id, void *vdata);
|
|
|
|
static const struct handlerdef gl_funcs_serv[] = {
|
|
{MSG_ADDFILES, srv_addfile},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
static const struct handlerdef gl_funcs_addfile[] = {
|
|
{MSG_VERSION, afc_version},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
/* this is only used on the server */
|
|
static char *gl_sockpath = NULL;
|
|
|
|
void
|
|
ipc_socket_setup(void *parent, add_torrents_func_t addfunc, void *cbdata) {
|
|
struct constate *con;
|
|
|
|
con = g_new0(struct constate, 1);
|
|
con->source = NULL;
|
|
con->fd = -1;
|
|
con->funcs = gl_funcs_serv;
|
|
con->type = CON_SERV;
|
|
con->u.serv.wind = parent;
|
|
con->u.serv.addfunc = addfunc;
|
|
con->u.serv.cbdata = cbdata;
|
|
|
|
serv_bind(con);
|
|
}
|
|
|
|
gboolean
|
|
ipc_sendfiles_blocking(GList *files) {
|
|
struct constate *con;
|
|
char *path;
|
|
gboolean ret = FALSE;
|
|
|
|
con = g_new0(struct constate, 1);
|
|
con->source = NULL;
|
|
con->fd = -1;
|
|
con->funcs = gl_funcs_addfile;
|
|
con->type = CON_ADDFILE;
|
|
con->u.addfile.loop = g_main_loop_new(NULL, TRUE);
|
|
con->u.addfile.files = files;
|
|
con->u.addfile.succeeded = &ret;
|
|
con->u.addfile.addid = 0;
|
|
|
|
path = cf_sockname();
|
|
if(!client_connect(path, con)) {
|
|
g_free(path);
|
|
destroycon(con);
|
|
return FALSE;
|
|
}
|
|
|
|
g_main_loop_run(con->u.addfile.loop);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* open a local socket for clients connections */
|
|
static void
|
|
serv_bind(struct constate *con) {
|
|
struct sockaddr_un sa;
|
|
|
|
rmsock();
|
|
gl_sockpath = cf_sockname();
|
|
|
|
if(0 > (con->fd = socket(AF_LOCAL, SOCK_STREAM, 0)))
|
|
goto fail;
|
|
|
|
bzero(&sa, sizeof(sa));
|
|
sa.sun_family = AF_LOCAL;
|
|
strncpy(sa.sun_path, gl_sockpath, sizeof(sa.sun_path) - 1);
|
|
|
|
/* unlink any existing socket file before trying to create ours */
|
|
unlink(gl_sockpath);
|
|
if(0 > bind(con->fd, (struct sockaddr *)&sa, SUN_LEN(&sa))) {
|
|
/* bind may fail if there was already a socket, so try twice */
|
|
unlink(gl_sockpath);
|
|
if(0 > bind(con->fd, (struct sockaddr *)&sa, SUN_LEN(&sa)))
|
|
goto fail;
|
|
}
|
|
|
|
if(0 > listen(con->fd, 5))
|
|
goto fail;
|
|
|
|
con->source = io_new_listening(con->fd, sizeof(struct sockaddr_un),
|
|
srv_io_accept, all_io_closed, con);
|
|
|
|
g_atexit(rmsock);
|
|
|
|
return;
|
|
|
|
fail:
|
|
errmsg(con->u.serv.wind, _("Failed to set up socket:\n%s"),
|
|
strerror(errno));
|
|
if(0 <= con->fd)
|
|
close(con->fd);
|
|
con->fd = -1;
|
|
rmsock();
|
|
}
|
|
|
|
static void
|
|
rmsock(void) {
|
|
if(NULL != gl_sockpath) {
|
|
unlink(gl_sockpath);
|
|
g_free(gl_sockpath);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
client_connect(char *path, struct constate *con) {
|
|
struct sockaddr_un addr;
|
|
|
|
if(0 > (con->fd = socket(AF_UNIX, SOCK_STREAM, 0))) {
|
|
fprintf(stderr, _("failed to create socket: %s\n"), strerror(errno));
|
|
return FALSE;
|
|
}
|
|
|
|
bzero(&addr, sizeof(addr));
|
|
addr.sun_family = AF_UNIX;
|
|
strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
|
|
|
|
if(0 > connect(con->fd, (struct sockaddr*)&addr, SUN_LEN(&addr))) {
|
|
fprintf(stderr, _("failed to connect to %s: %s\n"), path, strerror(errno));
|
|
return FALSE;
|
|
}
|
|
|
|
con->source = io_new(con->fd, afc_io_sent, all_io_received,
|
|
all_io_closed, con);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
srv_io_accept(GSource *source SHUTUP, int fd, struct sockaddr *sa SHUTUP,
|
|
socklen_t len SHUTUP, void *vdata) {
|
|
struct constate *con = vdata;
|
|
struct constate *newcon;
|
|
|
|
newcon = g_new(struct constate, 1);
|
|
memcpy(newcon, con, sizeof(*newcon));
|
|
newcon->fd = fd;
|
|
newcon->source = io_new(fd, NULL, all_io_received, all_io_closed, newcon);
|
|
|
|
if(NULL != newcon->source)
|
|
send_msg_int(newcon, MSG_VERSION, PROTOVERS);
|
|
else {
|
|
g_free(newcon);
|
|
close(fd);
|
|
}
|
|
}
|
|
|
|
static int
|
|
send_msg(struct constate *con, const char *name, benc_val_t *val) {
|
|
char *buf;
|
|
size_t used, total;
|
|
benc_val_t dict;
|
|
char stupid;
|
|
|
|
/* construct a dictionary value */
|
|
/* XXX I shouldn't be constructing benc_val_t's by hand */
|
|
bzero(&dict, sizeof(dict));
|
|
dict.type = TYPE_DICT;
|
|
dict.val.l.alloc = 2;
|
|
dict.val.l.count = 2;
|
|
dict.val.l.vals = g_new0(benc_val_t, 2);
|
|
dict.val.l.vals[0].type = TYPE_STR;
|
|
dict.val.l.vals[0].val.s.i = strlen(name);
|
|
dict.val.l.vals[0].val.s.s = (char*)name;
|
|
dict.val.l.vals[1] = *val;
|
|
|
|
/* bencode the dictionary, starting at offset 8 in the buffer */
|
|
buf = malloc(9);
|
|
g_assert(NULL != buf);
|
|
total = 9;
|
|
used = 8;
|
|
if(tr_bencSave(&dict, &buf, &used, &total)) {
|
|
g_assert_not_reached();
|
|
}
|
|
g_free(dict.val.l.vals);
|
|
|
|
/* write the bencoded data length into the first 8 bytes of the buffer */
|
|
stupid = buf[8];
|
|
snprintf(buf, 9, "%08X", used - 8);
|
|
buf[8] = stupid;
|
|
|
|
/* send the data */
|
|
return io_send_keepdata(con->source, buf, used);
|
|
}
|
|
|
|
static int
|
|
send_msg_int(struct constate *con, const char *name, int num) {
|
|
benc_val_t val;
|
|
|
|
bzero(&val, sizeof(val));
|
|
val.type = TYPE_INT;
|
|
val.val.i = num;
|
|
|
|
return send_msg(con, name, &val);
|
|
}
|
|
|
|
static unsigned int
|
|
all_io_received(GSource *source, char *data, unsigned int len, void *vdata) {
|
|
struct constate *con = vdata;
|
|
size_t size;
|
|
char stupid;
|
|
char *end;
|
|
benc_val_t msgs;
|
|
int ii, jj;
|
|
|
|
if(9 > len)
|
|
return 0;
|
|
|
|
stupid = data[8];
|
|
data[8] = '\0';
|
|
size = strtoul(data, NULL, 16);
|
|
data[8] = stupid;
|
|
|
|
if(size + 8 > len)
|
|
return 0;
|
|
|
|
if(!tr_bencLoad(data + 8, size, &msgs, &end) && TYPE_DICT == msgs.type) {
|
|
for(ii = 0; msgs.val.l.count > ii + 1; ii += 2)
|
|
if(TYPE_STR == msgs.val.l.vals[ii].type)
|
|
for(jj = 0; NULL != con->funcs[jj].name; jj++)
|
|
if(0 == strcmp(msgs.val.l.vals[ii].val.s.s, con->funcs[jj].name)) {
|
|
con->funcs[jj].func(con, msgs.val.l.vals[ii].val.s.s,
|
|
msgs.val.l.vals + ii + 1);
|
|
break;
|
|
}
|
|
tr_bencFree(&msgs);
|
|
}
|
|
|
|
return size + 8 +
|
|
all_io_received(source, data + size + 8, len - size - 8, con);
|
|
}
|
|
|
|
static void
|
|
destroycon(struct constate *con) {
|
|
con->source = NULL;
|
|
|
|
if(0 <= con->fd)
|
|
close(con->fd);
|
|
con->fd = -1;
|
|
|
|
switch(con->type) {
|
|
case CON_SERV:
|
|
break;
|
|
case CON_ADDFILE:
|
|
freestrlist(con->u.addfile.files);
|
|
g_main_loop_quit(con->u.addfile.loop);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
all_io_closed(GSource *source SHUTUP, void *vdata) {
|
|
struct constate *con = vdata;
|
|
|
|
destroycon(con);
|
|
}
|
|
|
|
static void
|
|
srv_addfile(struct constate *con, const char *name SHUTUP, benc_val_t *val) {
|
|
struct constate_serv *srv = &con->u.serv;
|
|
GList *files;
|
|
int ii;
|
|
|
|
if(TYPE_LIST == val->type) {
|
|
files = NULL;
|
|
for(ii = 0; ii < val->val.l.count; ii++)
|
|
if(TYPE_STR == val->val.l.vals[ii].type &&
|
|
/* XXX somehow escape invalid utf-8 */
|
|
g_utf8_validate(val->val.l.vals[ii].val.s.s, -1, NULL))
|
|
files = g_list_append(files, val->val.l.vals[ii].val.s.s);
|
|
srv->addfunc(srv->cbdata, NULL, files, NULL, NULL);
|
|
g_list_free(files);
|
|
}
|
|
}
|
|
|
|
static void
|
|
afc_version(struct constate *con, const char *name SHUTUP, benc_val_t *val) {
|
|
struct constate_addfile *afc = &con->u.addfile;
|
|
GList *file;
|
|
benc_val_t list, *str;
|
|
|
|
if(TYPE_INT != val->type || PROTOVERS != val->val.i) {
|
|
fprintf(stderr, _("bad IPC protocol version\n"));
|
|
destroycon(con);
|
|
} else {
|
|
/* XXX handle getting a non-version tag, invalid data,
|
|
or nothing (read timeout) */
|
|
bzero(&list, sizeof(list));
|
|
list.type = TYPE_LIST;
|
|
list.val.l.alloc = g_list_length(afc->files);
|
|
list.val.l.vals = g_new0(benc_val_t, list.val.l.alloc);
|
|
for(file = afc->files; NULL != file; file = file->next) {
|
|
str = list.val.l.vals + list.val.l.count;
|
|
str->type = TYPE_STR;
|
|
str->val.s.i = strlen(file->data);
|
|
str->val.s.s = file->data;
|
|
list.val.l.count++;
|
|
}
|
|
g_list_free(afc->files);
|
|
afc->files = NULL;
|
|
afc->addid = send_msg(con, MSG_ADDFILES, &list);
|
|
tr_bencFree(&list);
|
|
}
|
|
}
|
|
|
|
static void
|
|
afc_io_sent(GSource *source SHUTUP, unsigned int id, void *vdata) {
|
|
struct constate_addfile *afc = &((struct constate*)vdata)->u.addfile;
|
|
|
|
if(0 < id && afc->addid == id) {
|
|
*(afc->succeeded) = TRUE;
|
|
destroycon(vdata);
|
|
}
|
|
}
|