irssi/src/irc/dcc/dcc-send.c
Timo Sirainen dc8bd638e3 Irssi now uses 64bit file offets if it's only supported by system. Also did
a few changes to DCC so that it should be possible to send >4GB files.

DCC protocol uses 32bit "n bytes transferred" notifications, so I had to
bend the protocol a bit to allow 64bit files by truncating the value to
lowest 32bits. I'm not sure how other clients handle those notifications,
but irssi uses it only to figure out when the DCC SEND transfer is complete,
so it's quite safe to assume that if we've managed to write() all the bytes
and we receive the last 32bit of file size, it means the total file size
instead of the total - (n+1)*4GB.


git-svn-id: http://svn.irssi.org/repos/irssi/trunk@3018 dbcabf3a-b0e7-0310-adc4-f8d773084564
2002-11-21 17:48:40 +00:00

430 lines
11 KiB
C

/*
dcc-send.c : irssi
Copyright (C) 1999-2001 Timo Sirainen
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "module.h"
#include "signals.h"
#include "commands.h"
#include "network.h"
#include "net-sendbuffer.h"
#include "misc.h"
#include "settings.h"
#include "irc-servers.h"
#include "dcc-send.h"
#include "dcc-chat.h"
#include "dcc-queue.h"
#include <glob.h>
#ifndef GLOB_TILDE
# define GLOB_TILDE 0 /* unsupported */
#endif
static int dcc_send_one_file(int queue, const char *target, const char *fname,
IRC_SERVER_REC *server, CHAT_DCC_REC *chat);
static void dcc_queue_send_next(int queue)
{
IRC_SERVER_REC *server;
DCC_QUEUE_REC *qrec;
int send_started = FALSE;
while ((qrec = dcc_queue_get_next(queue)) != NULL && !send_started) {
server = qrec->servertag == NULL ? NULL :
IRC_SERVER(server_find_tag(qrec->servertag));
if (server == NULL && qrec->chat == NULL) {
/* no way to send this request */
signal_emit("dcc error send no route", 2,
qrec->nick, qrec->file);
} else {
send_started = dcc_send_one_file(queue, qrec->nick,
qrec->file, server,
qrec->chat);
}
dcc_queue_remove_head(queue);
}
if (!send_started) {
/* no files in queue anymore, remove it */
dcc_queue_free(queue);
}
}
static void dcc_send_add(const char *servertag, CHAT_DCC_REC *chat,
const char *nick, char *fileargs, int add_mode)
{
struct stat st;
glob_t globbuf;
char *fname;
int i, ret, files, flags, queue, start_new_transfer;
globbuf.gl_offs = 0;
flags = GLOB_NOCHECK | GLOB_TILDE;
/* this loop parses all <file> parameters and adds them to glubbuf */
for (;;) {
fname = cmd_get_quoted_param(&fileargs);
if (*fname == '\0')
break;
if (glob(fname, flags, 0, &globbuf) < 0)
break;
/* this flag must not be set before first call to glob!
(man glob) */
flags |= GLOB_APPEND;
}
files = 0; queue = -1; start_new_transfer = 0;
/* add all globbed files to a proper queue */
for (i = 0; i < globbuf.gl_pathc; i++) {
const char *fname = globbuf.gl_pathv[i];
ret = stat(fname, &st);
if (ret == 0 && S_ISDIR(st.st_mode)) {
/* we don't want directories */
errno = EISDIR;
ret = -1;
}
if (ret < 0) {
signal_emit("dcc error file open", 3,
nick, fname, errno);
continue;
}
if (queue < 0) {
/* in append and prepend mode try to find an
old queue. if an old queue is not found
create a new queue. if not in append or
prepend mode, create a new queue */
if (add_mode != DCC_QUEUE_NORMAL)
queue = dcc_queue_old(nick, servertag);
start_new_transfer = 0;
if (queue < 0) {
queue = dcc_queue_new();
start_new_transfer = 1;
}
}
dcc_queue_add(queue, add_mode, nick,
fname, servertag, chat);
files++;
}
if (files > 0 && start_new_transfer)
dcc_queue_send_next(queue);
globfree(&globbuf);
}
/* DCC SEND [-append | -prepend | -flush | -rmtail | -rmhead]
<nick> <file> [<file> ...] */
static void cmd_dcc_send(const char *data, IRC_SERVER_REC *server,
WI_ITEM_REC *item)
{
const char *servertag;
char *nick, *fileargs;
void *free_arg;
CHAT_DCC_REC *chat;
GHashTable *optlist;
int queue, mode;
if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTIONS |
PARAM_FLAG_GETREST, "dcc send",
&optlist, &nick, &fileargs))
return;
chat = item_get_dcc(item);
if (chat != NULL &&
(chat->mirc_ctcp || g_strcasecmp(nick, chat->nick) != 0))
chat = NULL;
if (!IS_IRC_SERVER(server) || !server->connected)
servertag = NULL;
else
servertag = server->tag;
if (servertag == NULL && chat == NULL)
cmd_param_error(CMDERR_NOT_CONNECTED);
if (g_hash_table_lookup(optlist, "rmhead") != NULL) {
queue = dcc_queue_old(nick, servertag);
if (queue != -1)
dcc_queue_remove_head(queue);
} else if (g_hash_table_lookup(optlist, "rmtail") != NULL) {
queue = dcc_queue_old(nick, servertag);
if (queue != -1)
dcc_queue_remove_tail(queue);
} else if (g_hash_table_lookup(optlist, "flush") != NULL) {
queue = dcc_queue_old(nick, servertag);
if (queue != -1)
dcc_queue_free(queue);
} else {
if (g_hash_table_lookup(optlist, "append") != NULL)
mode = DCC_QUEUE_APPEND;
else if (g_hash_table_lookup(optlist, "prepend") != NULL)
mode = DCC_QUEUE_PREPEND;
else
mode = DCC_QUEUE_NORMAL;
if (*fileargs == '\0')
cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
dcc_send_add(servertag, chat, nick, fileargs, mode);
}
cmd_params_free(free_arg);
}
static SEND_DCC_REC *dcc_send_create(IRC_SERVER_REC *server,
CHAT_DCC_REC *chat,
const char *nick, const char *arg)
{
SEND_DCC_REC *dcc;
dcc = g_new0(SEND_DCC_REC, 1);
dcc->orig_type = module_get_uniq_id_str("DCC", "GET");
dcc->type = module_get_uniq_id_str("DCC", "SEND");
dcc->fhandle = -1;
dcc->queue = -1;
dcc_init_rec(DCC(dcc), server, chat, nick, arg);
return dcc;
}
static void sig_dcc_destroyed(SEND_DCC_REC *dcc)
{
if (!IS_DCC_SEND(dcc)) return;
if (dcc->fhandle != -1)
close(dcc->fhandle);
dcc_queue_send_next(dcc->queue);
}
/* input function: DCC SEND - we're ready to send more data */
static void dcc_send_data(SEND_DCC_REC *dcc)
{
char buffer[512];
int ret;
ret = read(dcc->fhandle, buffer, sizeof(buffer));
if (ret <= 0) {
/* no need to call this function anymore..
in fact it just eats all the cpu.. */
dcc->waitforend = TRUE;
g_source_remove(dcc->tagwrite);
dcc->tagwrite = -1;
return;
}
ret = net_transmit(dcc->handle, buffer, ret);
if (ret > 0) dcc->transfd += ret;
dcc->gotalldata = FALSE;
lseek(dcc->fhandle, dcc->transfd, SEEK_SET);
signal_emit("dcc transfer update", 1, dcc);
}
/* input function: DCC SEND - received some data */
static void dcc_send_read_size(SEND_DCC_REC *dcc)
{
guint32 bytes;
int ret;
ret = net_receive(dcc->handle, dcc->count_buf+dcc->count_pos,
4-dcc->count_pos);
if (ret == -1) {
dcc_close(DCC(dcc));
return;
}
dcc->count_pos += ret;
if (dcc->count_pos != 4)
return;
bytes = ntohl(*((guint32 *) dcc->count_buf));
dcc->count_pos = 0;
if (dcc->waitforend && bytes == (dcc->transfd & 0xffffffff)) {
/* file is sent */
dcc->gotalldata = TRUE;
dcc_close(DCC(dcc));
}
}
/* input function: DCC SEND - someone tried to connect to our socket */
static void dcc_send_connected(SEND_DCC_REC *dcc)
{
GIOChannel *handle;
IPADDR addr;
int port;
/* accept connection */
handle = net_accept(dcc->handle, &addr, &port);
if (handle == NULL)
return;
/* TODO: some kind of paranoia check would be nice. it would check
that the host of the nick who we sent the request matches the
address who connected us. */
net_disconnect(dcc->handle);
g_source_remove(dcc->tagconn);
dcc->tagconn = -1;
dcc->starttime = time(NULL);
dcc->handle = handle;
memcpy(&dcc->addr, &addr, sizeof(IPADDR));
net_ip2host(&dcc->addr, dcc->addrstr);
dcc->port = port;
dcc->tagread = g_input_add(handle, G_INPUT_READ,
(GInputFunction) dcc_send_read_size, dcc);
dcc->tagwrite = g_input_add(handle, G_INPUT_WRITE,
(GInputFunction) dcc_send_data, dcc);
signal_emit("dcc connected", 1, dcc);
}
static char *dcc_send_get_file(const char *fname)
{
char *str, *path;
str = convert_home(fname);
if (!g_path_is_absolute(str)) {
/* full path not given to file, use dcc_upload_path */
g_free(str);
path = convert_home(settings_get_str("dcc_upload_path"));
str = *path == '\0' ? g_strdup(fname) :
g_strconcat(path, G_DIR_SEPARATOR_S, fname, NULL);
g_free(path);
}
return str;
}
static int dcc_send_one_file(int queue, const char *target, const char *fname,
IRC_SERVER_REC *server, CHAT_DCC_REC *chat)
{
struct stat st;
char *str;
char host[MAX_IP_LEN];
int hfile, port;
SEND_DCC_REC *dcc;
IPADDR own_ip;
GIOChannel *handle;
if (dcc_find_request(DCC_SEND_TYPE, target, fname)) {
signal_emit("dcc error send exists", 2, target, fname);
return FALSE;
}
str = dcc_send_get_file(fname);
hfile = open(str, O_RDONLY);
g_free(str);
if (hfile == -1) {
signal_emit("dcc error file open", 3, target, fname,
GINT_TO_POINTER(errno));
return FALSE;
}
if (fstat(hfile, &st) < 0) {
g_warning("fstat() failed: %s", strerror(errno));
close(hfile);
return FALSE;
}
/* start listening */
handle = dcc_listen(chat != NULL ? chat->handle :
net_sendbuffer_handle(server->handle),
&own_ip, &port);
if (handle == NULL) {
close(hfile);
g_warning("dcc_listen() failed: %s", strerror(errno));
return FALSE;
}
fname = g_basename(fname);
/* Replace all the spaces with underscore so that lesser
intellgent clients can communicate.. */
if (!settings_get_bool("dcc_send_replace_space_with_underscore"))
str = NULL;
else {
str = g_strdup(fname);
g_strdelimit(str, " ", '_');
fname = str;
}
dcc = dcc_send_create(server, chat, target, fname);
g_free(str);
dcc->handle = handle;
dcc->port = port;
dcc->size = st.st_size;
dcc->fhandle = hfile;
dcc->queue = queue;
dcc->file_quoted = strchr(fname, ' ') != NULL;
dcc->tagconn = g_input_add(handle, G_INPUT_READ,
(GInputFunction) dcc_send_connected, dcc);
/* send DCC request */
signal_emit("dcc request send", 1, dcc);
dcc_ip2str(&own_ip, host);
str = g_strdup_printf(dcc->file_quoted ?
"DCC SEND \"%s\" %s %d %"PRIuUOFF_T :
"DCC SEND %s %s %d %"PRIuUOFF_T,
dcc->arg, host, port, dcc->size);
dcc_ctcp_message(server, target, chat, FALSE, str);
g_free(str);
return TRUE;
}
void dcc_send_init(void)
{
dcc_register_type("SEND");
settings_add_str("dcc", "dcc_upload_path", "~");
settings_add_bool("dcc", "dcc_send_replace_space_with_underscore", FALSE);
signal_add("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed);
command_bind("dcc send", NULL, (SIGNAL_FUNC) cmd_dcc_send);
command_set_options("dcc send", "append flush prepend rmhead rmtail");
dcc_queue_init();
}
void dcc_send_deinit(void)
{
dcc_queue_deinit();
dcc_unregister_type("SEND");
signal_remove("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed);
command_unbind("dcc send", (SIGNAL_FUNC) cmd_dcc_send);
}