1
0
mirror of https://github.com/irssi/irssi.git synced 2024-12-04 14:46:39 -05:00
irssi/src/core/net-nonblock.c
Wouter Coekaerts 9c2d621cc6 Oops. Update address correctly now.
git-svn-id: http://svn.irssi.org/repos/irssi/trunk@4490 dbcabf3a-b0e7-0310-adc4-f8d773084564
2007-05-08 18:41:10 +00:00

295 lines
6.9 KiB
C

/*
net-nonblock.c : Nonblocking net_connect()
Copyright (C) 1998-2000 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.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "module.h"
#include <signal.h>
#include "pidwait.h"
#include "net-nonblock.h"
typedef struct {
NET_CALLBACK func;
void *data;
GIOChannel *pipes[2];
int port;
IPADDR *my_ip;
int tag;
} SIMPLE_THREAD_REC;
#define is_fatal_error(err) \
(err != 0 && err != G_IO_ERROR_AGAIN && errno != EINTR)
static int g_io_channel_write_block(GIOChannel *channel, void *data, int len)
{
gsize ret;
int err, sent;
sent = 0;
do {
err = g_io_channel_write(channel, (char *) data + sent,
len-sent, &ret);
sent += ret;
} while (sent < len && !is_fatal_error(err));
return err != 0 ? -1 : 0;
}
static int g_io_channel_read_block(GIOChannel *channel, void *data, int len)
{
time_t maxwait;
gsize ret;
int err, received;
maxwait = time(NULL)+2;
received = 0;
do {
err = g_io_channel_read(channel, (char *) data + received,
len-received, &ret);
received += ret;
} while (received < len && time(NULL) < maxwait &&
(ret != 0 || !is_fatal_error(err)));
return received < len ? -1 : 0;
}
/* nonblocking gethostbyname(), ip (IPADDR) + error (int, 0 = not error) is
written to pipe when found PID of the resolver child is returned */
int net_gethostbyname_nonblock(const char *addr, GIOChannel *pipe,
int reverse_lookup)
{
RESOLVED_IP_REC rec;
const char *errorstr;
#ifndef WIN32
int pid;
#endif
int len;
g_return_val_if_fail(addr != NULL, FALSE);
#ifndef WIN32
pid = fork();
if (pid > 0) {
/* parent */
pidwait_add(pid);
return pid;
}
if (pid != 0) {
/* failed! */
g_warning("net_connect_thread(): fork() failed! "
"Using blocking resolving");
}
#endif
/* child */
srand(time(NULL));
memset(&rec, 0, sizeof(rec));
rec.error = net_gethostbyname(addr, &rec.ip4, &rec.ip6);
if (rec.error == 0) {
errorstr = NULL;
if (reverse_lookup) {
/* reverse lookup the IP, ignore any error */
if (rec.ip4.family != 0)
net_gethostbyaddr(&rec.ip4, &rec.host4);
if (rec.ip6.family != 0)
net_gethostbyaddr(&rec.ip6, &rec.host6);
}
} else {
errorstr = net_gethosterror(rec.error);
rec.errlen = errorstr == NULL ? 0 : strlen(errorstr)+1;
}
g_io_channel_write_block(pipe, &rec, sizeof(rec));
if (rec.errlen != 0)
g_io_channel_write_block(pipe, (void *) errorstr, rec.errlen);
else {
if (rec.host4) {
len = strlen(rec.host4) + 1;
g_io_channel_write_block(pipe, (void *) &len,
sizeof(int));
g_io_channel_write_block(pipe, (void *) rec.host4,
len);
}
if (rec.host6) {
len = strlen(rec.host6) + 1;
g_io_channel_write_block(pipe, (void *) &len,
sizeof(int));
g_io_channel_write_block(pipe, (void *) rec.host6,
len);
}
}
#ifndef WIN32
if (pid == 0)
_exit(99);
#endif
/* we used blocking lookup */
return 0;
}
/* get the resolved IP address */
int net_gethostbyname_return(GIOChannel *pipe, RESOLVED_IP_REC *rec)
{
int len;
rec->error = -1;
rec->errorstr = NULL;
rec->host4 = NULL;
rec->host6 = NULL;
#ifndef WIN32
fcntl(g_io_channel_unix_get_fd(pipe), F_SETFL, O_NONBLOCK);
#endif
/* get ip+error */
if (g_io_channel_read_block(pipe, rec, sizeof(*rec)) == -1) {
rec->errorstr = g_strdup_printf("Host name lookup: %s",
g_strerror(errno));
return -1;
}
if (rec->error) {
/* read error string, if we can't read everything for some
reason, just ignore it. */
rec->errorstr = g_malloc0(rec->errlen+1);
g_io_channel_read_block(pipe, rec->errorstr, rec->errlen);
} else {
if (rec->host4) {
g_io_channel_read_block(pipe, &len, sizeof(int));
rec->host4 = g_malloc0(len);
g_io_channel_read_block(pipe, rec->host4, len);
}
if (rec->host6) {
g_io_channel_read_block(pipe, &len, sizeof(int));
rec->host6 = g_malloc0(len);
g_io_channel_read_block(pipe, rec->host6, len);
}
}
return 0;
}
/* Get host name, call func when finished */
int net_gethostbyaddr_nonblock(IPADDR *ip, NET_HOST_CALLBACK func, void *data)
{
/* FIXME: not implemented */
return FALSE;
}
/* Kill the resolver child */
void net_disconnect_nonblock(int pid)
{
g_return_if_fail(pid > 0);
#ifndef WIN32
kill(pid, SIGKILL);
#endif
}
static void simple_init(SIMPLE_THREAD_REC *rec, GIOChannel *handle)
{
g_return_if_fail(rec != NULL);
g_source_remove(rec->tag);
if (net_geterror(handle) != 0) {
/* failed */
g_io_channel_close(handle);
g_io_channel_unref(handle);
handle = NULL;
}
rec->func(handle, rec->data);
g_free(rec);
}
static void simple_readpipe(SIMPLE_THREAD_REC *rec, GIOChannel *pipe)
{
RESOLVED_IP_REC iprec;
GIOChannel *handle;
IPADDR *ip;
g_return_if_fail(rec != NULL);
g_source_remove(rec->tag);
net_gethostbyname_return(pipe, &iprec);
g_free_not_null(iprec.errorstr);
g_io_channel_close(rec->pipes[0]);
g_io_channel_unref(rec->pipes[0]);
g_io_channel_close(rec->pipes[1]);
g_io_channel_unref(rec->pipes[1]);
ip = iprec.ip4.family != 0 ? &iprec.ip4 : &iprec.ip6;
handle = iprec.error == -1 ? NULL :
net_connect_ip(ip, rec->port, rec->my_ip);
g_free_not_null(rec->my_ip);
if (handle == NULL) {
/* failed */
rec->func(NULL, rec->data);
g_free(rec);
return;
}
rec->tag = g_input_add(handle, G_INPUT_READ | G_INPUT_WRITE,
(GInputFunction) simple_init, rec);
}
/* Connect to server, call func when finished */
int net_connect_nonblock(const char *server, int port, const IPADDR *my_ip,
NET_CALLBACK func, void *data)
{
SIMPLE_THREAD_REC *rec;
int fd[2];
g_return_val_if_fail(server != NULL, FALSE);
g_return_val_if_fail(func != NULL, FALSE);
if (pipe(fd) != 0) {
g_warning("net_connect_nonblock(): pipe() failed.");
return FALSE;
}
rec = g_new0(SIMPLE_THREAD_REC, 1);
rec->port = port;
if (my_ip != NULL) {
rec->my_ip = g_malloc(sizeof(IPADDR));
memcpy(rec->my_ip, my_ip, sizeof(IPADDR));
}
rec->func = func;
rec->data = data;
rec->pipes[0] = g_io_channel_unix_new(fd[0]);
rec->pipes[1] = g_io_channel_unix_new(fd[1]);
/* start nonblocking host name lookup */
net_gethostbyname_nonblock(server, rec->pipes[1], 0);
rec->tag = g_input_add(rec->pipes[0], G_INPUT_READ,
(GInputFunction) simple_readpipe, rec);
return TRUE;
}